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

@@ -31,7 +31,7 @@ struct ScenarioDPlannerTests {
/// Creates a stadium at a known location
private func makeStadium(
id: UUID = UUID(),
id: String = "stadium_test_\(UUID().uuidString)",
city: String,
lat: Double,
lon: Double,
@@ -51,9 +51,9 @@ struct ScenarioDPlannerTests {
/// Creates a team
private func makeTeam(
id: UUID = UUID(),
id: String = "team_test_\(UUID().uuidString)",
name: String,
stadiumId: UUID,
stadiumId: String,
sport: Sport = .mlb
) -> Team {
Team(
@@ -71,10 +71,10 @@ struct ScenarioDPlannerTests {
/// Creates a game at a stadium
private func makeGame(
id: UUID = UUID(),
stadiumId: UUID,
homeTeamId: UUID,
awayTeamId: UUID,
id: String = "game_test_\(UUID().uuidString)",
stadiumId: String,
homeTeamId: String,
awayTeamId: String,
dateTime: Date,
sport: Sport = .mlb
) -> Game {
@@ -93,10 +93,10 @@ struct ScenarioDPlannerTests {
private func makePlanningRequest(
startDate: Date,
endDate: Date,
followTeamId: UUID?,
followTeamId: String?,
allGames: [Game],
stadiums: [UUID: Stadium],
teams: [UUID: Team] = [:],
stadiums: [String: Stadium],
teams: [String: Team] = [:],
selectedRegions: Set<Region> = [],
allowRepeatCities: Bool = true,
useHomeLocation: Bool = false,
@@ -132,12 +132,12 @@ struct ScenarioDPlannerTests {
@Test("D.1.1 - Single team with home games returns trip with those games")
func test_followTeam_HomeGames_ReturnsTrip() {
// Setup: Team with 2 home games
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
let team = makeTeam(id: teamId, name: "Chicago Cubs", stadiumId: stadiumId)
let game1 = makeGame(
@@ -179,8 +179,8 @@ struct ScenarioDPlannerTests {
@Test("D.1.2 - Team with away games includes those games")
func test_followTeam_AwayGames_IncludesAwayGames() {
// Setup: Team with one home game and one away game (2 cities for simpler route)
let homeStadiumId = UUID()
let awayStadiumId = UUID()
let homeStadiumId = "stadium_home_\(UUID().uuidString)"
let awayStadiumId = "stadium_away_\(UUID().uuidString)"
let homeStadium = makeStadium(id: homeStadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let awayStadium = makeStadium(id: awayStadiumId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
@@ -190,8 +190,8 @@ struct ScenarioDPlannerTests {
awayStadiumId: awayStadium
]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
// Home game
let homeGame = makeGame(
@@ -237,8 +237,8 @@ struct ScenarioDPlannerTests {
@Test("D.1.3 - Team games filtered by selected regions")
func test_followTeam_RegionFilter_FiltersGames() {
// Setup: Team with games in multiple regions
let eastStadiumId = UUID()
let centralStadiumId = UUID()
let eastStadiumId = "stadium_east_\(UUID().uuidString)"
let centralStadiumId = "stadium_central_\(UUID().uuidString)"
// East region (> -85 longitude)
let eastStadium = makeStadium(id: eastStadiumId, city: "New York", lat: 40.7128, lon: -73.9352)
@@ -247,8 +247,8 @@ struct ScenarioDPlannerTests {
let stadiums = [eastStadiumId: eastStadium, centralStadiumId: centralStadium]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
let eastGame = makeGame(
stadiumId: eastStadiumId,
@@ -292,14 +292,14 @@ struct ScenarioDPlannerTests {
@Test("D.2.1 - No team selected returns missingTeamSelection failure")
func test_followTeam_NoTeamSelected_ReturnsMissingTeamSelection() {
// Setup: No team ID
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let game = makeGame(
stadiumId: stadiumId,
homeTeamId: UUID(),
awayTeamId: UUID(),
homeTeamId: "team_test_\(UUID().uuidString)",
awayTeamId: "team_opponent_\(UUID().uuidString)",
dateTime: makeDate(day: 5, hour: 19)
)
@@ -323,17 +323,17 @@ struct ScenarioDPlannerTests {
@Test("D.2.2 - Team with no games in date range returns noGamesInRange failure")
func test_followTeam_NoGamesInRange_ReturnsNoGamesFailure() {
// Setup: Team's games are outside date range
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let teamId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
// Game is in July, but we search June
let game = makeGame(
stadiumId: stadiumId,
homeTeamId: teamId,
awayTeamId: UUID(),
awayTeamId: "team_opponent_\(UUID().uuidString)",
dateTime: makeDate(month: 7, day: 15, hour: 19)
)
@@ -357,13 +357,13 @@ struct ScenarioDPlannerTests {
@Test("D.2.3 - Team not involved in any games returns noGamesInRange failure")
func test_followTeam_TeamNotInGames_ReturnsNoGamesFailure() {
// Setup: Games exist but team isn't playing
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let teamId = UUID()
let otherTeam1 = UUID()
let otherTeam2 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let otherTeam1 = "team_other1_\(UUID().uuidString)"
let otherTeam2 = "team_other2_\(UUID().uuidString)"
// Game between other teams
let game = makeGame(
@@ -393,12 +393,12 @@ struct ScenarioDPlannerTests {
@Test("D.2.4 - Repeat city filter removes duplicate city visits")
func test_followTeam_RepeatCityFilter_RemovesDuplicates() {
// Setup: Team has multiple games at same stadium
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
let game1 = makeGame(
stadiumId: stadiumId,
@@ -445,16 +445,16 @@ struct ScenarioDPlannerTests {
@Test("D.2.5 - Missing date range returns missingDateRange failure")
func test_followTeam_MissingDateRange_ReturnsMissingDateRangeFailure() {
// Setup: Invalid date range (end before start)
let stadiumId = UUID()
let stadiumId = "stadium_test_\(UUID().uuidString)"
let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let stadiums = [stadiumId: stadium]
let teamId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let game = makeGame(
stadiumId: stadiumId,
homeTeamId: teamId,
awayTeamId: UUID(),
awayTeamId: "team_opponent_\(UUID().uuidString)",
dateTime: makeDate(day: 5, hour: 19)
)
@@ -481,16 +481,16 @@ struct ScenarioDPlannerTests {
@Test("D.3.1 - Route connects team games chronologically")
func test_followTeam_RouteIsChronological() {
// Setup: Team with games in 2 nearby cities chronologically
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)
let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
// Games in chronological order: Chicago Milwaukee
let game1 = makeGame(
@@ -534,16 +534,16 @@ struct ScenarioDPlannerTests {
@Test("D.3.2 - Travel segments connect stops correctly")
func test_followTeam_TravelSegmentsConnectStops() {
// Setup: Team with 2 games in different cities
let nycId = UUID()
let bostonId = UUID()
let nycId = "stadium_nyc_\(UUID().uuidString)"
let bostonId = "stadium_boston_\(UUID().uuidString)"
let nyc = makeStadium(id: nycId, city: "New York", lat: 40.7128, lon: -73.9352)
let boston = makeStadium(id: bostonId, city: "Boston", lat: 42.3601, lon: -71.0589)
let stadiums = [nycId: nyc, bostonId: boston]
let teamId = UUID()
let opponentId = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponentId = "team_opponent_\(UUID().uuidString)"
let game1 = makeGame(
stadiumId: nycId,
@@ -597,9 +597,9 @@ struct ScenarioDPlannerTests {
// Setup: Simulates Houston Chicago Anaheim (Astros July 20-29 scenario)
// Houston to Chicago: ~1000 miles, Chicago to Anaheim: ~2000 miles
// With 4+ days between each leg, both should be feasible
let houstonId = UUID()
let chicagoId = UUID()
let anaheimId = UUID()
let houstonId = "stadium_houston_\(UUID().uuidString)"
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let anaheimId = "stadium_anaheim_\(UUID().uuidString)"
let houston = makeStadium(id: houstonId, city: "Houston", lat: 29.7604, lon: -95.3698)
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
@@ -607,10 +607,10 @@ struct ScenarioDPlannerTests {
let stadiums = [houstonId: houston, chicagoId: chicago, anaheimId: anaheim]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let opponent3 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
let opponent3 = "team_opponent3_\(UUID().uuidString)"
// Houston home games: July 20-22
let houstonGame = makeGame(
@@ -674,9 +674,9 @@ struct ScenarioDPlannerTests {
func test_followTeam_ThreeCityRoute_InsufficientTime_ExcludesUnreachableCities() {
// Setup: Same cities but games too close together
// Chicago to Anaheim needs ~37 hours driving, but only 1 day between games
let houstonId = UUID()
let chicagoId = UUID()
let anaheimId = UUID()
let houstonId = "stadium_houston_\(UUID().uuidString)"
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let anaheimId = "stadium_anaheim_\(UUID().uuidString)"
let houston = makeStadium(id: houstonId, city: "Houston", lat: 29.7604, lon: -95.3698)
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
@@ -684,10 +684,10 @@ struct ScenarioDPlannerTests {
let stadiums = [houstonId: houston, chicagoId: chicago, anaheimId: anaheim]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let opponent3 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
let opponent3 = "team_opponent3_\(UUID().uuidString)"
// Houston: July 20
let houstonGame = makeGame(
@@ -744,17 +744,17 @@ struct ScenarioDPlannerTests {
func test_followTeam_PicksOptimalGamePerCity_ForRouteFeasibility() {
// Setup: Team has 3 games in each city (series)
// With allowRepeatCities=false, router should pick games that make the route work
let chicagoId = UUID()
let anaheimId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let anaheimId = "stadium_anaheim_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let anaheim = makeStadium(id: anaheimId, city: "Anaheim", lat: 33.8003, lon: -117.8827)
let stadiums = [chicagoId: chicago, anaheimId: anaheim]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
// Chicago series: July 24, 25, 26
let chicagoGame1 = makeGame(
@@ -826,8 +826,8 @@ struct ScenarioDPlannerTests {
func test_followTeam_FiveDaySegment_AtLimit_Succeeds() {
// Setup: ~38 hours of driving with exactly 5 days between games
// 5 days × 8 hours = 40 hours max, which should pass
let seattleId = UUID()
let miamiId = UUID()
let seattleId = "stadium_seattle_\(UUID().uuidString)"
let miamiId = "stadium_denver_\(UUID().uuidString)"
// Seattle to Miami: ~3,300 miles straight line × 1.3 = ~4,300 miles
// At 60 mph = ~72 hours - this is too far even for 5 days
@@ -837,9 +837,9 @@ struct ScenarioDPlannerTests {
let stadiums = [seattleId: seattle, miamiId: denver]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
let seattleGame = makeGame(
stadiumId: seattleId,
@@ -889,17 +889,17 @@ struct ScenarioDPlannerTests {
// Setup: Distance that would take > 40 hours to drive
// Seattle to Miami: ~3,300 miles straight line × 1.3 = ~4,300 miles
// At 60 mph = ~72 hours - exceeds 40 hour (5 day) limit
let seattleId = UUID()
let miamiId = UUID()
let seattleId = "stadium_seattle_\(UUID().uuidString)"
let miamiId = "stadium_miami_\(UUID().uuidString)"
let seattle = makeStadium(id: seattleId, city: "Seattle", lat: 47.6062, lon: -122.3321)
let miami = makeStadium(id: miamiId, city: "Miami", lat: 25.7617, lon: -80.1918)
let stadiums = [seattleId: seattle, miamiId: miami]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
let seattleGame = makeGame(
stadiumId: seattleId,
@@ -950,17 +950,17 @@ struct ScenarioDPlannerTests {
// Setup: Same ChicagoAnaheim route but with 2 drivers
// With 2 drivers × 8 hours = 16 hours/day
// Chicago to Anaheim in 3 days = 48 hours available (vs 24 hours with 1 driver)
let chicagoId = UUID()
let anaheimId = UUID()
let chicagoId = "stadium_chicago_\(UUID().uuidString)"
let anaheimId = "stadium_anaheim_\(UUID().uuidString)"
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
let anaheim = makeStadium(id: anaheimId, city: "Anaheim", lat: 33.8003, lon: -117.8827)
let stadiums = [chicagoId: chicago, anaheimId: anaheim]
let teamId = UUID()
let opponent1 = UUID()
let opponent2 = UUID()
let teamId = "team_test_\(UUID().uuidString)"
let opponent1 = "team_opponent1_\(UUID().uuidString)"
let opponent2 = "team_opponent2_\(UUID().uuidString)"
let chicagoGame = makeGame(
stadiumId: chicagoId,