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)
|
if (!doc.activatedMailAt) await sendK18(doc, project)
|
||||||
})
|
})
|
||||||
} else if ((doc.state === 'off' || doc.state === 'disabled') && SITE_KEY_RE.test(doc.siteKey || '')) {
|
} else if ((doc.state === 'off' || doc.state === 'disabled') && SITE_KEY_RE.test(doc.siteKey || '')) {
|
||||||
await analyseDeps.client.stopSite(doc.siteKey)
|
// Gleiche Sperre wie Ein-/Ausschalten, Dokument nachlesen: eine inzwischen neu aktivierte Analyse bleibt an
|
||||||
stats.stopped.push(doc.$id)
|
await withLocks([`analyse:${doc.projectId}`], async () => {
|
||||||
const marker = offMarker(doc)
|
const fresh = await listDocuments(config.collections.portalFeatures, [Query.equal('featureKey', FEATURE_KEY), Query.equal('projectId', doc.projectId), Query.limit(1)])
|
||||||
if (!doc.removalTicketId && marker >= config.analyse.avvVersion) {
|
const cur = fresh[0]
|
||||||
const project = await projectOf(doc.projectId)
|
if (!cur || cur.state === 'active' || !SITE_KEY_RE.test(cur.siteKey || '')) return
|
||||||
if (project && (await customerOf(doc.customerId))) {
|
await analyseDeps.client.stopSite(cur.siteKey)
|
||||||
await ensureRemovalTicket(doc, project)
|
stats.stopped.push(cur.$id)
|
||||||
stats.tickets.push(doc.$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) {
|
} catch (err) {
|
||||||
console.error(`[analyse] Abgleich für Projekt ${doc.projectId} fehlgeschlagen:`, err.message)
|
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.equal((await doc('kd2', 'kd2p')).state, 'off')
|
||||||
assert.ok(stops.includes(b.siteKey))
|
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