crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

GameStoreContributingDevicesTests.swift (3457B)


      1 import CoreData
      2 import Foundation
      3 import Testing
      4 
      5 @testable import Crossmate
      6 
      7 /// `GameStore.contributingDevices` enumerates the `(author, device)` pairs that
      8 /// wrote a `MovesEntity` for a game — the local, device-level signal replay uses
      9 /// to decide whether the local journal alone covers the grid (no other device →
     10 /// no CloudKit). It must distinguish this account's own second device, which the
     11 /// author-keyed roster cannot.
     12 @Suite("GameStore.contributingDevices", .isolatedNotificationState)
     13 @MainActor
     14 struct GameStoreContributingDevicesTests {
     15 
     16     private static let puzzleSource = """
     17     Title: Test Puzzle
     18     Author: Test
     19 
     20 
     21     AB
     22     CD
     23     """
     24 
     25     private func makeGame(in ctx: NSManagedObjectContext) throws -> (GameEntity, UUID) {
     26         let gameID = UUID()
     27         let entity = GameEntity(context: ctx)
     28         entity.id = gameID
     29         entity.title = "Test"
     30         entity.puzzleSource = Self.puzzleSource
     31         entity.createdAt = Date()
     32         entity.updatedAt = Date()
     33         entity.ckRecordName = "game-\(gameID.uuidString)"
     34         entity.ckZoneName = "game-\(gameID.uuidString)"
     35         entity.databaseScope = 1
     36         try ctx.save()
     37         return (entity, gameID)
     38     }
     39 
     40     private func addMoves(
     41         for entity: GameEntity,
     42         gameID: UUID,
     43         authorID: String,
     44         deviceID: String,
     45         in ctx: NSManagedObjectContext
     46     ) throws {
     47         let row = MovesEntity(context: ctx)
     48         row.game = entity
     49         row.authorID = authorID
     50         row.deviceID = deviceID
     51         row.cells = try MovesCodec.encode([:])
     52         row.updatedAt = Date()
     53         row.ckRecordName = RecordSerializer.recordName(
     54             forMovesInGame: gameID, authorID: authorID, deviceID: deviceID
     55         )
     56         try ctx.save()
     57     }
     58 
     59     @Test("empty when nobody has written")
     60     func emptyWhenNoMoves() throws {
     61         let persistence = makeTestPersistence()
     62         let store = makeTestStore(persistence: persistence)
     63         let (_, gameID) = try makeGame(in: persistence.viewContext)
     64 
     65         #expect(store.contributingDevices(for: gameID).isEmpty)
     66     }
     67 
     68     @Test("one author across two devices yields two device keys")
     69     func sameAuthorTwoDevices() throws {
     70         let persistence = makeTestPersistence()
     71         let store = makeTestStore(persistence: persistence)
     72         let (entity, gameID) = try makeGame(in: persistence.viewContext)
     73 
     74         try addMoves(for: entity, gameID: gameID, authorID: "alice", deviceID: "iphone", in: persistence.viewContext)
     75         try addMoves(for: entity, gameID: gameID, authorID: "alice", deviceID: "ipad", in: persistence.viewContext)
     76 
     77         let devices = store.contributingDevices(for: gameID)
     78         #expect(devices == [
     79             JournalDeviceKey(authorID: "alice", deviceID: "iphone"),
     80             JournalDeviceKey(authorID: "alice", deviceID: "ipad"),
     81         ])
     82     }
     83 
     84     @Test("distinct authors are kept separate")
     85     func distinctAuthors() throws {
     86         let persistence = makeTestPersistence()
     87         let store = makeTestStore(persistence: persistence)
     88         let (entity, gameID) = try makeGame(in: persistence.viewContext)
     89 
     90         try addMoves(for: entity, gameID: gameID, authorID: "alice", deviceID: "iphone", in: persistence.viewContext)
     91         try addMoves(for: entity, gameID: gameID, authorID: "bob", deviceID: "iphone", in: persistence.viewContext)
     92 
     93         #expect(store.contributingDevices(for: gameID).count == 2)
     94     }
     95 }