MotorSignal-App/test/group_ride_models_test.dart

282 lines
9.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:motorcycle_dash/services/group_rides/group_ride_models.dart';
void main() {
group('GroupRideLocation Model Tests', () {
test('serializes and deserializes meeting location correctly', () {
const loc = GroupRideLocation(
latitude: 34.5378,
longitude: -83.9849,
displayName: 'Dahlonega Square',
address: '123 Main St, Dahlonega, GA',
locationType: GroupRideLocationType.meetup,
notes: 'Meet near north corner',
stopDurationMinutes: 15,
);
final json = loc.toJson();
expect(json['latitude'], equals(34.5378));
expect(json['longitude'], equals(-83.9849));
expect(json['displayName'], equals('Dahlonega Square'));
expect(json['locationType'], equals('meetup'));
expect(json['stopDurationMinutes'], equals(15));
final restored = GroupRideLocation.fromJson(json);
expect(restored, equals(loc));
});
test('supports intermediate stops and location types', () {
expect(GroupRideLocationType.fuel.label, equals('Fuel Stop'));
expect(GroupRideLocationType.scenic.label, equals('Scenic Overlook'));
expect(GroupRideLocationType.food.label, equals('Food & Drink'));
});
});
group('GroupRide Model Tests', () {
test('serializes and deserializes full group ride', () {
final now = DateTime(2026, 9, 3, 10, 0);
final start = DateTime(2026, 9, 10, 9, 0);
final departure = DateTime(2026, 9, 10, 9, 30);
final ride = GroupRide(
id: 'ride_101',
organizerUid: 'alice',
organizerHandle: 'alice_apex',
organizerDisplayName: 'Alice Apex',
title: 'Blood Mountain Run',
description: 'Twisty mountain loop.',
visibility: GroupRideVisibility.friendsOnly,
joinPolicy: GroupRideJoinPolicy.inviteOnly,
status: GroupRideStatus.scheduled,
scheduledStartAt: start,
departureAt: departure,
participantLimit: 8,
participantCount: 3,
meetingPoint: const GroupRideLocation(
latitude: 34.5,
longitude: -83.9,
displayName: 'Meetup Chevron',
locationType: GroupRideLocationType.meetup,
),
destination: const GroupRideLocation(
latitude: 34.9,
longitude: -83.8,
displayName: 'Brasstown Bald',
locationType: GroupRideLocationType.destination,
),
stops: const [
GroupRideLocation(
latitude: 34.7,
longitude: -83.85,
displayName: 'Vogel State Park',
locationType: GroupRideLocationType.rest,
),
],
createdAt: now,
updatedAt: now,
);
final json = ride.toJson();
expect(json['id'], equals('ride_101'));
expect(json['organizerUid'], equals('alice'));
expect(json['title'], equals('Blood Mountain Run'));
expect(json['visibility'], equals('friendsOnly'));
expect(json['participantLimit'], equals(8));
expect(json['participantCount'], equals(3));
expect((json['stops'] as List).length, equals(1));
final restored = GroupRide.fromJson(json);
expect(restored.id, equals(ride.id));
expect(restored.organizerUid, equals(ride.organizerUid));
expect(restored.title, equals(ride.title));
expect(restored.visibility, equals(GroupRideVisibility.friendsOnly));
expect(restored.isFull, isFalse);
expect(restored.isCanceled, isFalse);
expect(restored.stops.length, equals(1));
expect(restored.stops.first.displayName, equals('Vogel State Park'));
});
test('participant capacity check respects limit', () {
final now = DateTime.now();
final ride = GroupRide(
id: 'ride_full',
organizerUid: 'alice',
organizerHandle: 'alice_apex',
organizerDisplayName: 'Alice',
title: 'Tight Crew',
scheduledStartAt: now,
departureAt: now,
participantLimit: 2,
participantCount: 2,
meetingPoint: const GroupRideLocation(
latitude: 0,
longitude: 0,
displayName: 'Base',
),
createdAt: now,
updatedAt: now,
);
expect(ride.isFull, isTrue);
});
});
group('GroupRideParticipant Model Tests', () {
test('serializes and deserializes participant', () {
final now = DateTime(2026, 9, 3, 12, 0);
final participant = GroupRideParticipant(
userId: 'bob',
role: GroupRideRole.participant,
status: ParticipantStatus.accepted,
riderHandle: 'bob_rider',
riderDisplayName: 'Bob Rider',
riderAvatarPreset: 'helmet_amber',
invitedBy: 'alice',
createdAt: now,
updatedAt: now,
);
final json = participant.toJson();
expect(json['userId'], equals('bob'));
expect(json['role'], equals('participant'));
expect(json['status'], equals('accepted'));
expect(json['riderHandle'], equals('bob_rider'));
expect(json['invitedBy'], equals('alice'));
final restored = GroupRideParticipant.fromJson(json);
expect(restored.userId, equals('bob'));
expect(restored.status.isActiveMember, isTrue);
expect(restored.displayHandle, equals('@bob_rider'));
});
});
group('LiveGroupPresence and Ride Lifecycle Tests', () {
test('verifies GroupRideStatus underway and isLiveEligible semantics', () {
expect(GroupRideStatus.underway.label, equals('Underway'));
final now = DateTime.now();
final rideScheduled = GroupRide(
id: 'r1',
organizerUid: 'alice',
organizerHandle: 'alice',
organizerDisplayName: 'Alice',
title: 'Run',
status: GroupRideStatus.scheduled,
scheduledStartAt: now,
departureAt: now,
meetingPoint: const GroupRideLocation(latitude: 0, longitude: 0, displayName: 'Point'),
createdAt: now,
updatedAt: now,
);
expect(rideScheduled.isLiveEligible, isTrue);
expect(rideScheduled.isUnderway, isFalse);
final rideUnderway = rideScheduled.copyWith(status: GroupRideStatus.underway);
expect(rideUnderway.isUnderway, isTrue);
expect(rideUnderway.isLiveEligible, isTrue);
final rideCompleted = rideScheduled.copyWith(status: GroupRideStatus.completed);
expect(rideCompleted.isLiveEligible, isFalse);
expect(rideCompleted.isUnderway, isFalse);
final rideCanceled = rideScheduled.copyWith(status: GroupRideStatus.canceled);
expect(rideCanceled.isLiveEligible, isFalse);
});
test('serializes and deserializes LiveGroupPresence correctly', () {
final now = DateTime.now();
final expires = now.add(const Duration(seconds: 120));
final presence = LiveGroupPresence(
userId: 'bob',
groupRideId: 'ride_101',
latitude: 34.5378,
longitude: -83.9849,
heading: 180.5,
speedMph: 48.2,
accuracyMeters: 4.5,
updatedAt: now,
expiresAt: expires,
sharingEnabled: true,
status: LivePresenceStatus.riding,
);
final json = presence.toJson();
expect(json['userId'], equals('bob'));
expect(json['groupRideId'], equals('ride_101'));
expect(json['latitude'], equals(34.5378));
expect(json['longitude'], equals(-83.9849));
expect(json['heading'], equals(180.5));
expect(json['speedMph'], equals(48.2));
expect(json['accuracyMeters'], equals(4.5));
expect(json['sharingEnabled'], isTrue);
expect(json['status'], equals('riding'));
final restored = LiveGroupPresence.fromJson(json);
expect(restored.userId, equals('bob'));
expect(restored.groupRideId, equals('ride_101'));
expect(restored.latitude, equals(34.5378));
expect(restored.longitude, equals(-83.9849));
expect(restored.status, equals(LivePresenceStatus.riding));
expect(restored.isValidCoordinates, isTrue);
expect(restored.isAccurate, isTrue);
expect(restored.isFresh, isTrue);
expect(restored.isStale, isFalse);
});
test('coordinate bounds validation rejects invalid coordinates', () {
final now = DateTime.now();
final invalidLat = LiveGroupPresence(
userId: 'bob',
groupRideId: 'r1',
latitude: 91.0,
longitude: 0.0,
updatedAt: now,
expiresAt: now.add(const Duration(seconds: 120)),
);
expect(invalidLat.isValidCoordinates, isFalse);
final invalidLng = LiveGroupPresence(
userId: 'bob',
groupRideId: 'r1',
latitude: 0.0,
longitude: 185.0,
updatedAt: now,
expiresAt: now.add(const Duration(seconds: 120)),
);
expect(invalidLng.isValidCoordinates, isFalse);
});
test('freshness and staleness calculation respects timestamp and sharing flag', () {
final now = DateTime.now();
final fresh = LiveGroupPresence(
userId: 'bob',
groupRideId: 'r1',
latitude: 35.0,
longitude: -84.0,
updatedAt: now.subtract(const Duration(seconds: 10)),
expiresAt: now.add(const Duration(seconds: 110)),
sharingEnabled: true,
);
expect(fresh.isFresh, isTrue);
expect(fresh.isStale, isFalse);
final stale = LiveGroupPresence(
userId: 'bob',
groupRideId: 'r1',
latitude: 35.0,
longitude: -84.0,
updatedAt: now.subtract(const Duration(seconds: 130)),
expiresAt: now.subtract(const Duration(seconds: 10)),
sharingEnabled: true,
);
expect(stale.isFresh, isFalse);
expect(stale.isStale, isTrue);
final disabled = fresh.copyWith(sharingEnabled: false);
expect(disabled.isFresh, isFalse);
expect(disabled.isStale, isTrue);
});
});
}