refactor: change domain model IDs from UUID to String canonical IDs

This refactor fixes the achievement system by using stable canonical string
IDs (e.g., "stadium_mlb_fenway_park") instead of random UUIDs. This ensures
stadium mappings for achievements are consistent across app launches and
CloudKit sync operations.

Changes:
- Stadium, Team, Game: id property changed from UUID to String
- Trip, TripStop, TripPreferences: updated to use String IDs for games/stadiums
- CKModels: removed UUID parsing, use canonical IDs directly
- AchievementEngine: now matches against canonical stadium IDs
- All test files updated to use String IDs instead of UUID()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-12 09:24:33 -06:00
parent 4b2cacaeba
commit 1703ca5b0f
53 changed files with 642 additions and 727 deletions

View File

@@ -29,7 +29,7 @@ struct GameDAGRouterTests {
// Create a stadium at a known location
private func makeStadium(
id: UUID = UUID(),
id: String = "stadium_test_\(UUID().uuidString)",
city: String,
lat: Double,
lon: Double
@@ -48,14 +48,14 @@ struct GameDAGRouterTests {
// Create a game at a stadium
private func makeGame(
id: UUID = UUID(),
stadiumId: UUID,
id: String = "game_test_\(UUID().uuidString)",
stadiumId: String,
dateTime: Date
) -> Game {
Game(
id: id,
homeTeamId: UUID(),
awayTeamId: UUID(),
homeTeamId: "team_test_\(UUID().uuidString)",
awayTeamId: "team_test_\(UUID().uuidString)",
stadiumId: stadiumId,
dateTime: dateTime,
sport: .mlb,
@@ -78,7 +78,7 @@ struct GameDAGRouterTests {
@Test("2.2 - Single game returns single route")
func test_findRoutes_SingleGame_ReturnsSingleRoute() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
@@ -95,7 +95,7 @@ struct GameDAGRouterTests {
@Test("2.3 - Single game with matching anchor returns single route")
func test_findRoutes_SingleGame_WithMatchingAnchor_ReturnsSingleRoute() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
@@ -112,10 +112,10 @@ struct GameDAGRouterTests {
@Test("2.4 - Single game with non-matching anchor returns empty")
func test_findRoutes_SingleGame_WithNonMatchingAnchor_ReturnsEmpty() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
let nonExistentAnchor = UUID()
let nonExistentAnchor = "stadium_nonexistent_\(UUID().uuidString)"
let routes = GameDAGRouter.findRoutes(
games: [game],
@@ -132,8 +132,8 @@ struct GameDAGRouterTests {
@Test("2.5 - Two games with feasible transition returns both in order")
func test_findRoutes_TwoGames_FeasibleTransition_ReturnsBothInOrder() {
// Chicago to Milwaukee is ~90 miles - easily feasible
let chicagoStadiumId = UUID()
let milwaukeeStadiumId = UUID()
let chicagoStadiumId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeStadiumId = "stadium_milwaukee_\(UUID().uuidString)"
let chicagoStadium = makeStadium(id: chicagoStadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukeeStadium = makeStadium(id: milwaukeeStadiumId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -162,8 +162,8 @@ struct GameDAGRouterTests {
@Test("2.6 - Two games with infeasible transition returns separate routes")
func test_findRoutes_TwoGames_InfeasibleTransition_ReturnsSeparateRoutes() {
// NYC to LA on same day is infeasible
let nycStadiumId = UUID()
let laStadiumId = UUID()
let nycStadiumId = "stadium_nyc_\(UUID().uuidString)"
let laStadiumId = "stadium_la_\(UUID().uuidString)"
let nycStadium = makeStadium(id: nycStadiumId, city: "New York", lat: 40.7128, lon: -73.9352)
let laStadium = makeStadium(id: laStadiumId, city: "Los Angeles", lat: 34.0522, lon: -118.2437)
@@ -191,7 +191,7 @@ struct GameDAGRouterTests {
@Test("2.7 - Two games same stadium same day (doubleheader) succeeds")
func test_findRoutes_TwoGames_SameStadiumSameDay_Succeeds() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
// Doubleheader: 1pm and 7pm same day, same stadium
@@ -219,7 +219,7 @@ struct GameDAGRouterTests {
@Test("2.8 - With anchors only returns routes containing all anchors")
func test_findRoutes_WithAnchors_OnlyReturnsRoutesContainingAllAnchors() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
@@ -246,8 +246,8 @@ struct GameDAGRouterTests {
@Test("2.9 - Impossible anchors returns empty")
func test_findRoutes_ImpossibleAnchors_ReturnsEmpty() {
// Two anchors at opposite ends of country on same day - impossible to attend both
let nycStadiumId = UUID()
let laStadiumId = UUID()
let nycStadiumId = "stadium_nyc_\(UUID().uuidString)"
let laStadiumId = "stadium_la_\(UUID().uuidString)"
let nycStadium = makeStadium(id: nycStadiumId, city: "New York", lat: 40.7128, lon: -73.9352)
let laStadium = makeStadium(id: laStadiumId, city: "Los Angeles", lat: 34.0522, lon: -118.2437)
@@ -271,9 +271,9 @@ struct GameDAGRouterTests {
@Test("2.10 - Multiple anchors route must contain all")
func test_findRoutes_MultipleAnchors_RouteMustContainAll() {
// Three games in nearby cities over 3 days - all feasible
let chicagoId = UUID()
let milwaukeeId = UUID()
let detroitId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
let detroitId = "stadium_detroit_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -306,7 +306,7 @@ struct GameDAGRouterTests {
@Test("2.11 - Allow repeat cities same city multiple days allowed")
func test_findRoutes_AllowRepeatCities_SameCityMultipleDays_Allowed() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
// Three games in Chicago over 3 days
@@ -330,8 +330,8 @@ struct GameDAGRouterTests {
@Test("2.12 - Disallow repeat cities skips second visit")
func test_findRoutes_DisallowRepeatCities_SkipsSecondVisit() {
let chicagoId = UUID()
let milwaukeeId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -360,7 +360,7 @@ struct GameDAGRouterTests {
@Test("2.13 - Disallow repeat cities only option is repeat overrides with warning")
func test_findRoutes_DisallowRepeatCities_OnlyOptionIsRepeat_OverridesWithWarning() {
// When only games available are in the same city, we still need to produce routes
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
// Only Chicago games available
@@ -388,8 +388,8 @@ struct GameDAGRouterTests {
@Test("2.14 - Exceeds max daily driving transition rejected")
func test_findRoutes_ExceedsMaxDailyDriving_TransitionRejected() {
// NYC to Denver is ~1,800 miles, way over 8 hours of driving (480 miles at 60mph)
let nycId = UUID()
let denverId = UUID()
let nycId = "stadium_nyc_\(UUID().uuidString)"
let denverId = "stadium_denver_\(UUID().uuidString)"
let nyc = makeStadium(id: nycId, city: "New York", lat: 40.7128, lon: -73.9352)
let denver = makeStadium(id: denverId, city: "Denver", lat: 39.7392, lon: -104.9903)
@@ -417,8 +417,8 @@ struct GameDAGRouterTests {
@Test("2.15 - Multi-day drive allowed if within daily limits")
func test_findRoutes_MultiDayDrive_Allowed_IfWithinDailyLimits() {
// NYC to Chicago is ~790 miles - doable over multiple days
let nycId = UUID()
let chicagoId = UUID()
let nycId = "stadium_nyc_\(UUID().uuidString)"
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let nyc = makeStadium(id: nycId, city: "New York", lat: 40.7128, lon: -73.9352)
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
@@ -443,8 +443,8 @@ struct GameDAGRouterTests {
@Test("2.16 - Same day different stadiums checks available time")
func test_findRoutes_SameDayDifferentStadiums_ChecksAvailableTime() {
// Chicago to Milwaukee is ~90 miles (~1.5 hours driving)
let chicagoId = UUID()
let milwaukeeId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -484,7 +484,7 @@ struct GameDAGRouterTests {
@Test("2.17 - Max day lookahead respects limit")
func test_findRoutes_MaxDayLookahead_RespectsLimit() {
// Games more than 5 days apart should not connect directly
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
@@ -514,7 +514,7 @@ struct GameDAGRouterTests {
@Test("2.18 - DST transition handles correctly")
func test_findRoutes_DSTTransition_HandlesCorrectly() {
// Test around DST transition (March 9, 2026 - spring forward)
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
// Create dates around DST transition
@@ -550,7 +550,7 @@ struct GameDAGRouterTests {
@Test("2.19 - Midnight game assigns to correct day")
func test_findRoutes_MidnightGame_AssignsToCorrectDay() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
// Game at 12:05 AM belongs to the new day
@@ -583,7 +583,7 @@ struct GameDAGRouterTests {
@Test("2.20 - Select diverse routes includes short and long trips")
func test_selectDiverseRoutes_ShortAndLongTrips_BothRepresented() {
// Create a mix of games over a week
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
var games: [Game] = []
@@ -611,9 +611,9 @@ struct GameDAGRouterTests {
@Test("2.21 - Select diverse routes includes high and low mileage")
func test_selectDiverseRoutes_HighAndLowMileage_BothRepresented() {
// Create games in both nearby and distant cities
let chicagoId = UUID()
let milwaukeeId = UUID()
let laId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
let laId = "stadium_la_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -647,11 +647,11 @@ struct GameDAGRouterTests {
("Cleveland", 41.4993, -81.6944),
]
var stadiums: [UUID: Stadium] = [:]
var stadiums: [String: Stadium] = [:]
var games: [Game] = []
for (index, city) in cities.enumerated() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
stadiums[stadiumId] = makeStadium(id: stadiumId, city: city.0, lat: city.1, lon: city.2)
games.append(makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: index + 1)))
}
@@ -675,7 +675,7 @@ struct GameDAGRouterTests {
@Test("2.23 - Select diverse routes deduplicates")
func test_selectDiverseRoutes_DuplicateRoutes_Deduplicated() {
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
@@ -693,7 +693,7 @@ struct GameDAGRouterTests {
// Check for duplicates
var seen = Set<String>()
for route in routes {
let key = route.map { $0.id.uuidString }.joined(separator: "-")
let key = route.map { $0.id }.joined(separator: "-")
#expect(!seen.contains(key), "Routes should be deduplicated")
seen.insert(key)
}
@@ -704,9 +704,9 @@ struct GameDAGRouterTests {
@Test("2.24 - Graph with potential cycle handles silently")
func test_findRoutes_GraphWithPotentialCycle_HandlesSilently() {
// Create a scenario where a naive algorithm might get stuck in a loop
let chicagoId = UUID()
let milwaukeeId = UUID()
let detroitId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
let detroitId = "stadium_detroit_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)