Analyse: Abgleich schaltet neu aktivierte Analysen nicht ab (Sperre + Nachlesen), weitere Abgleich-Tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
78170567b4
commit
495c3a2c40
2 files changed
+76
-9
No files matched your search
@@ -566,16 +566,21 @@ export async function runAnalyseReconcile({ now = new Date() } = {}) {
|
||||
if (!doc.activatedMailAt) await sendK18(doc, project)
|
||||
})
|
||||
} else if ((doc.state === 'off' || doc.state === 'disabled') && SITE_KEY_RE.test(doc.siteKey || '')) {
|
||||
await analyseDeps.client.stopSite(doc.siteKey)
|
||||
stats.stopped.push(doc.$id)
|
||||
const marker = offMarker(doc)
|
||||
if (!doc.removalTicketId && marker >= config.analyse.avvVersion) {
|
||||
const project = await projectOf(doc.projectId)
|
||||
if (project && (await customerOf(doc.customerId))) {
|
||||
await ensureRemovalTicket(doc, project)
|
||||
stats.tickets.push(doc.$id)
|
||||
// Gleiche Sperre wie Ein-/Ausschalten, Dokument nachlesen: eine inzwischen neu aktivierte Analyse bleibt an
|
||||
await withLocks([`analyse:${doc.projectId}`], async () => {
|
||||
const fresh = await listDocuments(config.collections.portalFeatures, [Query.equal('featureKey', FEATURE_KEY), Query.equal('projectId', doc.projectId), Query.limit(1)])
|
||||
const cur = fresh[0]
|
||||
if (!cur || cur.state === 'active' || !SITE_KEY_RE.test(cur.siteKey || '')) return
|
||||
await analyseDeps.client.stopSite(cur.siteKey)
|
||||
stats.stopped.push(cur.$id)
|
||||
if (!cur.removalTicketId && offMarker(cur) >= config.analyse.avvVersion) {
|
||||
const project = await projectOf(cur.projectId)
|
||||
if (project && (await customerOf(cur.customerId))) {
|
||||
await ensureRemovalTicket(cur, project)
|
||||
stats.tickets.push(cur.$id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[analyse] Abgleich für Projekt ${doc.projectId} fehlgeschlagen:`, err.message)
|
||||
|
||||
@@ -703,3 +703,65 @@ test('deleteCustomerWithPortalAccess: Analyse aus (kein Ticket, keine Mail), Lö
|
||||
assert.equal((await doc('kd2', 'kd2p')).state, 'off')
|
||||
assert.ok(stops.includes(b.siteKey))
|
||||
})
|
||||
|
||||
// ---- Fix round 1 ----
|
||||
test('Abgleich off-Zweig: wird das Dokument nach dem Lesen wieder aktiv, bleibt der Messdienst an (Sperre + Nachlesen)', async () => {
|
||||
const { withLocks } = await import('../server/services/keyedLock.js')
|
||||
aw.seed('websiteProjects', { $id: 'f1p', customerId: 'c1', projectName: 'F1', subdomain: 'f1', isPublic: true })
|
||||
const d = await seedFeature('c1', 'f1p', 'off', { offSince: '2026-10-04T09:00:00.000Z', removalTicketId: 'x' })
|
||||
let run
|
||||
await withLocks(['analyse:f1p'], async () => {
|
||||
run = an.runAnalyseReconcile({ now: NOW })
|
||||
await new Promise((r) => setTimeout(r, 150)) // Abgleich hat gelesen und wartet auf die Sperre
|
||||
await updateDocument(config.collections.portalFeatures, d.$id, { ...sf.stateFields('active') })
|
||||
})
|
||||
await run
|
||||
assert.ok(!stops.includes(d.siteKey), 'frisch aktivierte Analyse nicht abgeschaltet')
|
||||
assert.equal((await doc('c1', 'f1p')).state, 'active')
|
||||
})
|
||||
|
||||
test('Abgleich: Appwrite-Lesefehler (nicht 404) beendet nichts', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'f2p', customerId: 'c1', projectName: 'F2', subdomain: 'f2', isPublic: true })
|
||||
const d = await seedFeature('c1', 'f2p', 'active')
|
||||
const real = globalThis.fetch
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
if (String(url).includes('/collections/websiteProjects/documents/f2p')) return new Response(JSON.stringify({ message: 'kaputt' }), { status: 500, headers: { 'content-type': 'application/json' } })
|
||||
return real(url, opts)
|
||||
}
|
||||
try {
|
||||
await an.runAnalyseReconcile({ now: NOW })
|
||||
} finally {
|
||||
globalThis.fetch = real
|
||||
}
|
||||
assert.ok(!stops.includes(d.siteKey))
|
||||
assert.equal((await doc('c1', 'f2p')).state, 'active')
|
||||
})
|
||||
|
||||
test('Abgleich: nicht öffentliches Projekt / ohne Subdomain => beendet mit Entfernen-Ticket, ohne K19', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'f3p', customerId: 'c1', projectName: 'F3', subdomain: 'f3', isPublic: false, repoFullName: 'WEBklar/f3' })
|
||||
aw.seed('websiteProjects', { $id: 'f4p', customerId: 'c1', projectName: 'F4', isPublic: true })
|
||||
const a = await seedFeature('c1', 'f3p', 'active')
|
||||
const b = await seedFeature('c1', 'f4p', 'active')
|
||||
await an.runAnalyseReconcile({ now: NOW })
|
||||
for (const [p, name, key] of [['f3p', 'F3', a.siteKey], ['f4p', 'F4', b.siteKey]]) {
|
||||
const d = await doc('c1', p)
|
||||
assert.equal(d.state, 'off')
|
||||
assert.ok(stops.includes(key))
|
||||
assert.equal(removalTopics(name).length, 1)
|
||||
assert.ok(d.removalTicketId)
|
||||
}
|
||||
assert.equal(mails.sent.filter((m) => /beendet/.test(m.subject)).length, 0)
|
||||
})
|
||||
|
||||
test('Abgleich: stopSite wirft => Dokument bleibt unverändert aktiv', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'f5p', customerId: 'c1', projectName: 'F5', subdomain: 'f5', isPublic: false })
|
||||
const d = await seedFeature('c1', 'f5p', 'active')
|
||||
const real = an.analyseDeps.client.stopSite
|
||||
an.analyseDeps.client.stopSite = async () => { throw new Error('Messdienst weg') }
|
||||
await an.runAnalyseReconcile({ now: NOW })
|
||||
an.analyseDeps.client.stopSite = real
|
||||
const after = await doc('c1', 'f5p')
|
||||
assert.equal(after.state, 'active')
|
||||
assert.ok(!after.offSince)
|
||||
assert.equal(removalTopics('F5').length, 0)
|
||||
})
|
||||
Reference in new issue
Block a user