CI / build-and-deploy (pull_request) Successful in 30s
`startEnrichment` snapshotted `bookmarks.indices`, then awaited a network call per bookmark. `bookmarks` is replaced wholesale by loads, searches and the unread filter, so by the time the loop read `bookmarks[i]` the index could be out of range — EXC_BREAKPOINT in Array._checkSubscript, straight from the read. The write path already guarded with `i < bookmarks.count`; the read did not. `enrichAll()` had the same shape and the same exposure. Both now track bookmark ids and re-resolve the position after each await, via a shared `apply(summary:tags:toId:)` that skips a bookmark that is no longer loaded rather than writing to whatever now sits at that index — which is the other half of the bug: a stale-but-in-range index would have silently attached one bookmark's summary to another. Found while investigating leftover Spotlight "translation error ... Code=1 (null)" entries after the indexing fix. Those turned out to be a symptom, not a separate bug: the crash tore the process down mid-donation and the in-flight items failed to translate. With the crash fixed they are gone, and the suite went from crashing (0 tests executed) to green across three consecutive runs. Adds a source round-trip to SpotlightRetrievalTests, so both donation paths — app entities and raw searchable items — are covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
60 lines
2.5 KiB
Swift
60 lines
2.5 KiB
Swift
import Testing
|
|
import CoreSpotlight
|
|
@testable import Marks
|
|
|
|
/// End-to-end guard for the whole Spotlight path: index a bookmark, then
|
|
/// retrieve it the way the on-device assistant does.
|
|
///
|
|
/// Worth keeping. The bug this was written for made every item fail to
|
|
/// translate into the index while `indexAppEntities` still reported success,
|
|
/// so nothing short of a round trip would have caught it.
|
|
struct SpotlightRetrievalTests {
|
|
@Test func indexedBookmarkIsRetrievable() async throws {
|
|
let b = Bookmark(
|
|
id: 999_001,
|
|
url: "https://github.com/example/zqxjkltest",
|
|
title: "Zqxjkltest concurrency notes",
|
|
description: "A distinctive probe document about zqxjkltest.",
|
|
tagNames: ["zqxjkltest"],
|
|
dateAdded: Date(), dateModified: Date(),
|
|
isArchived: false, unread: false, shared: false,
|
|
websiteTitle: nil, websiteDescription: nil,
|
|
faviconUrl: nil, previewImageUrl: nil,
|
|
aiSummary: nil, aiTags: nil
|
|
)
|
|
await SpotlightIndexer.index([b])
|
|
var hits: [RetrievedBookmark] = []
|
|
for _ in 0..<20 {
|
|
try? await Task.sleep(for: .milliseconds(700))
|
|
hits = await SpotlightBookmarkSearch.run(query: "zqxjkltest", limit: 5)
|
|
if !hits.isEmpty { break }
|
|
}
|
|
await SpotlightIndexer.remove(ids: [999_001])
|
|
#expect(!hits.isEmpty, "Spotlight returned no hits for an indexed bookmark")
|
|
let hit = try #require(hits.first)
|
|
#expect(hit.title.contains("Zqxjkltest"))
|
|
#expect(hit.url == "https://github.com/example/zqxjkltest",
|
|
"url did not survive the round trip: \(hit.url)")
|
|
#expect(hit.host == "github.com", "host was \(hit.host)")
|
|
}
|
|
|
|
@Test func indexedSourceIsRetrievable() async throws {
|
|
let source = IngestedSource(
|
|
kind: .text,
|
|
title: "Wqvbnmtest meeting notes",
|
|
bodyText: "A distinctive probe document about wqvbnmtest.",
|
|
tags: ["wqvbnmtest"]
|
|
)
|
|
await SourceSpotlightIndexer.index([source])
|
|
var hits: [RetrievedBookmark] = []
|
|
for _ in 0..<20 {
|
|
try? await Task.sleep(for: .milliseconds(700))
|
|
hits = await SpotlightBookmarkSearch.run(query: "wqvbnmtest", limit: 5)
|
|
if !hits.isEmpty { break }
|
|
}
|
|
await SourceSpotlightIndexer.remove(ids: [source.id])
|
|
#expect(!hits.isEmpty, "Spotlight returned no hits for an indexed source")
|
|
#expect(hits.first?.title.contains("Wqvbnmtest") == true)
|
|
}
|
|
}
|