Complete rename across all bundle IDs, App Groups, CloudKit containers, StoreKit product IDs, data store filenames, URL schemes, logger subsystems, Swift identifiers, user-facing strings (7 languages), file names, directory names, Xcode project, schemes, assets, and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
142 lines
4.9 KiB
Swift
142 lines
4.9 KiB
Swift
//
|
|
// IntegrationTests.swift
|
|
// ReflectTests
|
|
//
|
|
// Integration tests verifying full lifecycle pipelines.
|
|
//
|
|
|
|
import XCTest
|
|
import SwiftData
|
|
@testable import Reflect
|
|
|
|
@MainActor
|
|
final class IntegrationTests: XCTestCase {
|
|
var sut: DataController!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
let schema = Schema([MoodEntryModel.self])
|
|
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
|
|
sut = DataController(container: try! ModelContainer(for: schema, configurations: [config]))
|
|
}
|
|
|
|
override func tearDown() {
|
|
sut = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: - Phase 8: Integration Pipelines
|
|
|
|
func test_fullCRUD_lifecycle() {
|
|
let date = makeDate(2024, 6, 15)
|
|
|
|
// Create
|
|
sut.add(mood: .good, forDate: date, entryType: .listView)
|
|
XCTAssertNotNil(sut.getEntry(byDate: date))
|
|
|
|
// Read
|
|
let entry = sut.getEntry(byDate: date)
|
|
XCTAssertEqual(entry?.mood, .good)
|
|
|
|
// Update
|
|
let updated = sut.update(entryDate: date, withMood: .great)
|
|
XCTAssertTrue(updated)
|
|
XCTAssertEqual(sut.getEntry(byDate: date)?.mood, .great)
|
|
|
|
// Update notes
|
|
sut.updateNotes(forDate: date, notes: "Lifecycle test")
|
|
XCTAssertEqual(sut.getEntry(byDate: date)?.notes, "Lifecycle test")
|
|
|
|
// Delete
|
|
sut.deleteAllEntries(forDate: date)
|
|
XCTAssertNil(sut.getEntry(byDate: date))
|
|
}
|
|
|
|
func test_streak_throughLifecycle() {
|
|
let today = Calendar.current.startOfDay(for: Date())
|
|
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
|
|
|
|
// Add today → streak=1
|
|
sut.add(mood: .good, forDate: today, entryType: .listView)
|
|
XCTAssertEqual(sut.calculateStreak(from: today).streak, 1)
|
|
|
|
// Add yesterday → streak=2
|
|
sut.add(mood: .great, forDate: yesterday, entryType: .listView)
|
|
XCTAssertEqual(sut.calculateStreak(from: today).streak, 2)
|
|
|
|
// Update today's mood → streak still 2
|
|
sut.update(entryDate: today, withMood: .average)
|
|
XCTAssertEqual(sut.calculateStreak(from: today).streak, 2)
|
|
|
|
// Delete today → streak=1 (counting from yesterday)
|
|
sut.deleteAllEntries(forDate: today)
|
|
XCTAssertEqual(sut.calculateStreak(from: today).streak, 1)
|
|
}
|
|
|
|
func test_voteStatus_throughLifecycle() {
|
|
let onboarding = OnboardingData()
|
|
onboarding.inputDay = .Today
|
|
// Set unlock time to 00:01 so we're always "after" unlock
|
|
var components = Calendar.current.dateComponents([.year, .month, .day], from: Date())
|
|
components.hour = 0
|
|
components.minute = 1
|
|
onboarding.date = Calendar.current.date(from: components)!
|
|
|
|
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: onboarding)
|
|
|
|
// Helper: check vote status using testable DataController
|
|
func isVoteNeeded() -> Bool {
|
|
let entry = sut.getEntry(byDate: votingDate.startOfDay)
|
|
return entry == nil || entry?.mood == .missing
|
|
}
|
|
|
|
// No entry → vote needed
|
|
XCTAssertTrue(isVoteNeeded())
|
|
|
|
// Add entry → no vote needed
|
|
sut.add(mood: .great, forDate: votingDate, entryType: .listView)
|
|
XCTAssertFalse(isVoteNeeded())
|
|
|
|
// Delete entry → vote needed again
|
|
sut.deleteAllEntries(forDate: votingDate)
|
|
XCTAssertTrue(isVoteNeeded())
|
|
}
|
|
|
|
func test_duplicateRemoval() {
|
|
let date = makeDate(2024, 6, 15)
|
|
|
|
// Manually insert 3 entries for same date (simulating CloudKit conflict)
|
|
let entry1 = MoodEntryModel(forDate: date, mood: .great, entryType: .listView)
|
|
entry1.timestamp = makeDate(2024, 6, 15, hour: 10)
|
|
sut.modelContext.insert(entry1)
|
|
|
|
let entry2 = MoodEntryModel(forDate: date, mood: .good, entryType: .listView)
|
|
entry2.timestamp = makeDate(2024, 6, 15, hour: 8)
|
|
sut.modelContext.insert(entry2)
|
|
|
|
let entry3 = MoodEntryModel(forDate: date, mood: .missing, entryType: .filledInMissing)
|
|
entry3.timestamp = makeDate(2024, 6, 15, hour: 12)
|
|
sut.modelContext.insert(entry3)
|
|
|
|
sut.save()
|
|
|
|
let removed = sut.removeDuplicates()
|
|
XCTAssertEqual(removed, 2)
|
|
XCTAssertEqual(sut.getAllEntries(byDate: date).count, 1)
|
|
}
|
|
|
|
func test_dataListeners_fireOnMutation() {
|
|
var listenerCallCount = 0
|
|
sut.addNewDataListener {
|
|
listenerCallCount += 1
|
|
}
|
|
|
|
// add() calls saveAndRunDataListeners internally
|
|
sut.add(mood: .good, forDate: makeDate(2024, 6, 15), entryType: .listView)
|
|
|
|
// add() does: delete-save (if existing) + insert + saveAndRunDataListeners
|
|
// For a fresh add with no existing entry, listener fires once from saveAndRunDataListeners
|
|
XCTAssertGreaterThanOrEqual(listenerCallCount, 1, "Listener should fire at least once on mutation")
|
|
}
|
|
}
|