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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user