Revert "hogar compartido: colaboracion entre cuentas sobre Firestore"
El hogar compartido cambia donde viven los datos, asi que se va a la 3.0 y la 2.x queda para correcciones. El trabajo sigue vivo en la rama 3.0.0 (163fd60). Importante para esta rama: esto tambien retira el entitlement com.apple.developer.applesignin, que sin la capability marcada en el App ID habria roto la firma al subir la 2.1.1 a TestFlight. This reverts commit163fd60. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
This commit is contained in:
@@ -1,241 +0,0 @@
|
||||
import XCTest
|
||||
import SwiftData
|
||||
@testable import MealMood
|
||||
|
||||
/// The pure half of the household sync: document ids, fingerprints and the
|
||||
/// model ↔ Firestore mapping. Anything touching the network stays out.
|
||||
final class HouseholdDocumentsTests: XCTestCase {
|
||||
|
||||
/// Held for the duration of the test: releasing the container tears the
|
||||
/// store off the coordinator and the next SwiftData call traps.
|
||||
private var container: ModelContainer!
|
||||
private var context: ModelContext!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
container = try ModelContainer(
|
||||
for: AppSettings.self, Dish.self, Tag.self, WeekPlan.self, MealSlot.self, ShoppingItem.self,
|
||||
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
|
||||
)
|
||||
context = ModelContext(container)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
context = nil
|
||||
container = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Document ids
|
||||
|
||||
func testWeekKeyIsTheMondayDate() {
|
||||
let monday = Calendar(identifier: .gregorian)
|
||||
.date(from: DateComponents(year: 2026, month: 9, day: 14))!
|
||||
|
||||
XCTAssertEqual(HouseholdDocuments.weekKey(for: monday), "2026-09-14")
|
||||
}
|
||||
|
||||
func testWeekKeyIsStableAcrossLocales() {
|
||||
let monday = Calendar(identifier: .gregorian)
|
||||
.date(from: DateComponents(year: 2026, month: 1, day: 5))!
|
||||
|
||||
// Two members with different phone languages must write the same
|
||||
// document, not one each.
|
||||
XCTAssertEqual(HouseholdDocuments.weekKey(for: monday), "2026-01-05")
|
||||
}
|
||||
|
||||
func testSlotIdIsDerivedFromContent() {
|
||||
XCTAssertEqual(HouseholdDocuments.slotId(dayOfWeek: 5, mealType: "dinner"), "5-dinner")
|
||||
XCTAssertEqual(HouseholdDocuments.slotId(dayOfWeek: 0, mealType: "breakfast"), "0-breakfast")
|
||||
}
|
||||
|
||||
// MARK: - Fingerprints
|
||||
|
||||
func testFingerprintIgnoresKeyOrder() {
|
||||
let a: [String: Any] = ["name": "Tortilla", "isPriority": true, "sortOrder": 3]
|
||||
let b: [String: Any] = ["sortOrder": 3, "name": "Tortilla", "isPriority": true]
|
||||
|
||||
XCTAssertEqual(HouseholdDocuments.fingerprint(a), HouseholdDocuments.fingerprint(b))
|
||||
}
|
||||
|
||||
func testFingerprintChangesWithContent() {
|
||||
let dish = Dish(name: "Tortilla")
|
||||
context.insert(dish)
|
||||
let before = HouseholdDocuments.fingerprint(HouseholdDocuments.fields(for: dish))
|
||||
|
||||
dish.name = "Tortilla de patatas"
|
||||
let after = HouseholdDocuments.fingerprint(HouseholdDocuments.fields(for: dish))
|
||||
|
||||
XCTAssertNotEqual(before, after, "an edited dish must be detected as a local change")
|
||||
}
|
||||
|
||||
func testFingerprintDistinguishesBoolFromInt() {
|
||||
let asBool: [String: Any] = ["value": true]
|
||||
let asInt: [String: Any] = ["value": 1]
|
||||
|
||||
XCTAssertNotEqual(HouseholdDocuments.fingerprint(asBool), HouseholdDocuments.fingerprint(asInt))
|
||||
}
|
||||
|
||||
// MARK: - Round trips
|
||||
|
||||
func testDishSurvivesARoundTrip() {
|
||||
let tagId = UUID()
|
||||
let original = Dish(
|
||||
name: "Salmón al horno",
|
||||
descriptionText: "con limón",
|
||||
tagIds: [tagId],
|
||||
isPriority: true,
|
||||
ingredients: ["salmón", "limón"],
|
||||
fixedDayOfWeek: 4,
|
||||
fixedMealType: "dinner"
|
||||
)
|
||||
context.insert(original)
|
||||
|
||||
let copy = Dish(id: original.id, name: "")
|
||||
context.insert(copy)
|
||||
HouseholdDocuments.apply(HouseholdDocuments.fields(for: original), to: copy)
|
||||
|
||||
XCTAssertEqual(copy.name, "Salmón al horno")
|
||||
XCTAssertEqual(copy.descriptionText, "con limón")
|
||||
XCTAssertEqual(copy.tagIds, [tagId])
|
||||
XCTAssertEqual(copy.ingredients, ["salmón", "limón"])
|
||||
XCTAssertTrue(copy.isPriority)
|
||||
XCTAssertEqual(copy.fixedDayOfWeek, 4)
|
||||
XCTAssertEqual(copy.fixedMealType, "dinner")
|
||||
XCTAssertEqual(
|
||||
HouseholdDocuments.fingerprint(HouseholdDocuments.fields(for: original)),
|
||||
HouseholdDocuments.fingerprint(HouseholdDocuments.fields(for: copy)),
|
||||
"a round trip must not look like a change to push"
|
||||
)
|
||||
}
|
||||
|
||||
func testTagRulesSurviveARoundTrip() {
|
||||
let original = Tag(
|
||||
name: "Pescado", nameEN: "Fish", color: "#4FC3F7",
|
||||
maxPerWeek: 2, noConsecutive: true, noDuplicateInDay: true,
|
||||
mealTypeRestriction: "dinner", dayRestriction: "weekdays",
|
||||
isDefault: false, sortOrder: 7
|
||||
)
|
||||
context.insert(original)
|
||||
|
||||
let copy = Tag(id: original.id, name: "", color: "")
|
||||
context.insert(copy)
|
||||
HouseholdDocuments.apply(HouseholdDocuments.fields(for: original), to: copy)
|
||||
|
||||
XCTAssertEqual(copy.nameEN, "Fish")
|
||||
XCTAssertEqual(copy.maxPerWeek, 2)
|
||||
XCTAssertTrue(copy.noConsecutive)
|
||||
XCTAssertEqual(copy.mealTypeRestriction, "dinner")
|
||||
XCTAssertEqual(copy.dayRestriction, "weekdays", "day rules must travel or they'd silently reset")
|
||||
XCTAssertEqual(copy.sortOrder, 7)
|
||||
}
|
||||
|
||||
func testSlotSurvivesARoundTripWithoutTheCalendarEventId() {
|
||||
let original = MealSlot(dayOfWeek: 2, mealType: "lunch")
|
||||
original.dishId = UUID()
|
||||
original.secondaryDishId = UUID()
|
||||
original.isSkipped = true
|
||||
original.calendarEventId = "local-eventkit-id"
|
||||
context.insert(original)
|
||||
|
||||
let copy = MealSlot(dayOfWeek: 2, mealType: "lunch")
|
||||
context.insert(copy)
|
||||
HouseholdDocuments.apply(HouseholdDocuments.fields(for: original), to: copy)
|
||||
|
||||
XCTAssertEqual(copy.dishId, original.dishId)
|
||||
XCTAssertEqual(copy.secondaryDishId, original.secondaryDishId)
|
||||
XCTAssertTrue(copy.isSkipped)
|
||||
// The EventKit id only means something on the device that created it.
|
||||
XCTAssertNil(copy.calendarEventId)
|
||||
XCTAssertNil(HouseholdDocuments.fields(for: original)["calendarEventId"])
|
||||
}
|
||||
|
||||
func testWeekPlanCarriesItsScheduleOverride() {
|
||||
let plan = WeekPlan(weekStartDate: Date().startOfWeek())
|
||||
plan.includeWeekendsOverride = true
|
||||
plan.mealTypesOverrideRaw = "breakfast,dinner"
|
||||
plan.userRating = 1
|
||||
context.insert(plan)
|
||||
|
||||
let copy = WeekPlan(weekStartDate: plan.weekStartDate)
|
||||
context.insert(copy)
|
||||
HouseholdDocuments.apply(HouseholdDocuments.fields(for: plan), to: copy)
|
||||
|
||||
XCTAssertEqual(copy.includeWeekendsOverride, true)
|
||||
XCTAssertEqual(copy.mealTypesOverrideRaw, "breakfast,dinner")
|
||||
XCTAssertEqual(copy.userRating, 1)
|
||||
}
|
||||
|
||||
func testShoppingItemSurvivesARoundTrip() {
|
||||
let item = ShoppingItem(weekStartDate: Date().startOfWeek(), title: "Leche", isChecked: true, sortOrder: 4)
|
||||
context.insert(item)
|
||||
|
||||
let copy = ShoppingItem(id: item.id, weekStartDate: item.weekStartDate, title: "")
|
||||
context.insert(copy)
|
||||
HouseholdDocuments.apply(HouseholdDocuments.fields(for: item), to: copy)
|
||||
|
||||
XCTAssertEqual(copy.title, "Leche")
|
||||
XCTAssertTrue(copy.isChecked)
|
||||
XCTAssertEqual(copy.sortOrder, 4)
|
||||
}
|
||||
|
||||
// MARK: - Invite codes
|
||||
|
||||
func testInviteCodesUseAnUnambiguousAlphabet() {
|
||||
for _ in 0..<200 {
|
||||
let code = HouseholdService.makeInviteCode()
|
||||
XCTAssertEqual(code.count, 6)
|
||||
// No 0/O/1/I and no vowels: codes get read out loud and retyped.
|
||||
XCTAssertFalse(code.contains(where: { "01IOAEU".contains($0) }), "ambiguous character in \(code)")
|
||||
}
|
||||
}
|
||||
|
||||
func testCodeNormalizationAcceptsHowPeopleTypeIt() {
|
||||
let code = HouseholdService.makeInviteCode()
|
||||
let messy = code.lowercased().split(separator: "").joined()
|
||||
let spaced = code.map(String.init).joined(separator: " ")
|
||||
let dashed = code.prefix(3) + "-" + code.suffix(3)
|
||||
|
||||
XCTAssertEqual(HouseholdService.normalize(messy), code)
|
||||
XCTAssertEqual(HouseholdService.normalize(spaced), code)
|
||||
XCTAssertEqual(HouseholdService.normalize(String(dashed)), code)
|
||||
XCTAssertTrue(HouseholdService.isPlausibleCode(spaced))
|
||||
}
|
||||
|
||||
func testShortOrEmptyCodesAreRejectedBeforeHittingTheNetwork() {
|
||||
XCTAssertFalse(HouseholdService.isPlausibleCode(""))
|
||||
XCTAssertFalse(HouseholdService.isPlausibleCode("BCD"))
|
||||
XCTAssertFalse(HouseholdService.isPlausibleCode("AEIOU"), "vowels aren't part of the alphabet")
|
||||
}
|
||||
|
||||
// MARK: - Shadow
|
||||
|
||||
func testShadowTracksWhatWasSynced() {
|
||||
var shadow = SyncShadow()
|
||||
shadow.set(path: "dishes/abc", fingerprint: "1234")
|
||||
|
||||
XCTAssertEqual(shadow.fingerprint(for: "dishes/abc"), "1234")
|
||||
XCTAssertNil(shadow.fingerprint(for: "dishes/other"))
|
||||
|
||||
shadow.remove(path: "dishes/abc")
|
||||
XCTAssertNil(shadow.fingerprint(for: "dishes/abc"))
|
||||
|
||||
shadow.set(path: "tags/x", fingerprint: "ffff")
|
||||
shadow.reset()
|
||||
XCTAssertTrue(shadow.paths.isEmpty)
|
||||
}
|
||||
|
||||
func testShadowSurvivesARelaunch() {
|
||||
let householdId = "test-\(UUID().uuidString)"
|
||||
var shadow = SyncShadow()
|
||||
shadow.set(path: "dishes/abc", fingerprint: "1234")
|
||||
shadow.save(householdId: householdId)
|
||||
|
||||
var restored = SyncShadow()
|
||||
restored.load(householdId: householdId)
|
||||
|
||||
XCTAssertEqual(restored.fingerprint(for: "dishes/abc"), "1234",
|
||||
"without this the whole store would re-upload on every launch")
|
||||
UserDefaults.standard.removeObject(forKey: "household_shadow_\(householdId)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user