Funktionen: disable() idempotent, disabledReason, Schema-Skript feature-setup
disable() laeuft unter einer Sperre je Dokument und aendert ein schon
abgeschaltetes Dokument nicht mehr (disabledAt als Beginn der 30-Tage-Frist,
Grund und Notiz bleiben); nur Altbestand ohne disabledAt bekommt dieses Feld.
Neu: DISABLED_REASONS und der Parameter reason (Vorgabe team, unbekannt = 400),
disableCustomerFeatures reicht ihn durch, die Kundenloeschung nutzt
kunde_geloescht. activate() leert zusaetzlich disabledReason und aboEndeMailAt
und laesst zahlungMeldung unberuehrt.
scripts/feature-setup.mjs legt portalFeatures.disabledReason, aboEndeMailAt und
zahlungMeldung idempotent an (--check meldet Luecken mit Exit 1);
schemaKeys('portalFeatures') zieht die Schluessel mit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39225594e5
commit
aaed273b62
9 files changed
+472
-18
No files matched your search
@@ -0,0 +1,130 @@
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
// Das Skript laedt config.js erst in main(): schema.js (Testhilfe) importiert es vor useTestEnv()
|
||||
import { PORTAL_FEATURE_ATTRIBUTES, main } from '../scripts/feature-setup.mjs'
|
||||
|
||||
// Schema-Skript der Funktions-Attribute (scripts/feature-setup.mjs): Mock-Client wie in analyseSetup.test.js
|
||||
const schema = { portalFeatures: [] } // wird im Schema-Test gefuellt (die Analyse-Schluessel kennt erst das dynamisch geladene Skript)
|
||||
const aw = await startAppwriteMock({ schema })
|
||||
after(() => aw.close())
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||||
const { config } = await import('../server/config.js')
|
||||
const { ANALYSE_ATTRIBUTES } = await import('../scripts/analyse-setup.mjs')
|
||||
|
||||
function mockClient({ attrs = [] } = {}) {
|
||||
const calls = { attrs: [], collections: [], indexes: [], waits: [] }
|
||||
return {
|
||||
calls,
|
||||
async collectionExists() {
|
||||
return true
|
||||
},
|
||||
async ensureCollection(id, name) {
|
||||
calls.collections.push({ id, name })
|
||||
return true
|
||||
},
|
||||
async listAttributes(id) {
|
||||
assert.equal(id, config.collections.portalFeatures, 'liest nur die Funktions-Collection')
|
||||
return attrs.map((key) => ({ key }))
|
||||
},
|
||||
async createAttribute(id, attr) {
|
||||
calls.attrs.push({ id, ...attr })
|
||||
},
|
||||
async waitForAttribute(id, key) {
|
||||
calls.waits.push(`${id}.${key}`)
|
||||
},
|
||||
async listIndexes() {
|
||||
return []
|
||||
},
|
||||
async createIndex(id, index) {
|
||||
calls.indexes.push(`${id}.${index.key}`)
|
||||
},
|
||||
}
|
||||
}
|
||||
const KEYS = ['disabledReason', 'aboEndeMailAt', 'zahlungMeldung']
|
||||
const KEINE = { attrs: [], collections: [], indexes: [], waits: [] }
|
||||
|
||||
test('Schema: drei Attribute mit Typ und Groesse, alle optional', () => {
|
||||
assert.deepEqual(PORTAL_FEATURE_ATTRIBUTES.map((a) => a.key), KEYS)
|
||||
const nach = Object.fromEntries(PORTAL_FEATURE_ATTRIBUTES.map((a) => [a.key, a]))
|
||||
assert.equal(nach.disabledReason.type, 'string')
|
||||
assert.equal(nach.disabledReason.size, 32)
|
||||
assert.ok(nach.disabledReason.size >= 'abo_ende_vor_einrichtung'.length, 'der laengste Abschaltgrund passt hinein')
|
||||
assert.ok(nach.disabledReason.size >= 24)
|
||||
assert.equal(nach.aboEndeMailAt.type, 'datetime')
|
||||
assert.equal('size' in nach.aboEndeMailAt, false, 'ein datetime-Attribut hat keine Groesse')
|
||||
assert.equal(nach.zahlungMeldung.type, 'string')
|
||||
assert.equal(nach.zahlungMeldung.size, 24)
|
||||
assert.ok(nach.zahlungMeldung.size >= 'bereits_aktiv'.length)
|
||||
assert.ok(PORTAL_FEATURE_ATTRIBUTES.every((a) => a.required === false), 'alle optional')
|
||||
})
|
||||
|
||||
test('Schema: DISABLED_REASONS passen in disabledReason', async () => {
|
||||
const { DISABLED_REASONS } = await import('../server/services/siteFeatures.js')
|
||||
const groesse = PORTAL_FEATURE_ATTRIBUTES.find((a) => a.key === 'disabledReason').size
|
||||
for (const grund of DISABLED_REASONS) assert.ok(grund.length <= groesse, grund)
|
||||
})
|
||||
|
||||
test('main legt nur Fehlendes an und wartet auf die neuen Attribute', async () => {
|
||||
const client = mockClient({ attrs: ['disabledReason', 'customerId'] })
|
||||
const log = []
|
||||
const result = await main({ client, log: (zeile) => log.push(zeile) })
|
||||
assert.deepEqual(client.calls.attrs.map((a) => `${a.id}.${a.key}`), ['portalFeatures.aboEndeMailAt', 'portalFeatures.zahlungMeldung'])
|
||||
assert.deepEqual(client.calls.attrs.map((a) => a.type), ['datetime', 'string'])
|
||||
assert.ok(client.calls.attrs.every((a) => a.required === false))
|
||||
assert.equal(client.calls.attrs.find((a) => a.key === 'zahlungMeldung').size, 24)
|
||||
assert.deepEqual(client.calls.waits, ['portalFeatures.aboEndeMailAt', 'portalFeatures.zahlungMeldung'])
|
||||
assert.deepEqual([client.calls.collections, client.calls.indexes], [[], []])
|
||||
assert.deepEqual(result.missing.map((a) => a.key), ['aboEndeMailAt', 'zahlungMeldung'])
|
||||
assert.ok(log.some((zeile) => zeile.includes('+ Attribut portalFeatures.aboEndeMailAt')))
|
||||
assert.ok(log.some((zeile) => zeile.includes('= portalFeatures.disabledReason (existiert)')))
|
||||
})
|
||||
|
||||
test('main legt bei leerer Collection alle drei an, beim zweiten Lauf nichts mehr (idempotent)', async () => {
|
||||
const leer = mockClient()
|
||||
assert.equal((await main({ client: leer, log: () => {} })).missing.length, 3)
|
||||
assert.deepEqual(leer.calls.attrs.map((a) => a.key), KEYS)
|
||||
|
||||
const voll = mockClient({ attrs: KEYS })
|
||||
const result = await main({ client: voll, log: () => {} })
|
||||
assert.deepEqual(voll.calls, KEINE)
|
||||
assert.equal(result.missing.length, 0)
|
||||
})
|
||||
|
||||
test('main --check legt nichts an, meldet jede Luecke und liefert sie im Ergebnis', async () => {
|
||||
const client = mockClient({ attrs: ['disabledReason'] })
|
||||
const log = []
|
||||
const result = await main({ check: true, client, log: (zeile) => log.push(zeile) })
|
||||
assert.deepEqual(client.calls, KEINE)
|
||||
assert.deepEqual(result.missing.map((a) => a.key), ['aboEndeMailAt', 'zahlungMeldung'])
|
||||
assert.ok(log.includes('- portalFeatures.aboEndeMailAt FEHLT'))
|
||||
assert.ok(log.includes('- portalFeatures.zahlungMeldung FEHLT'))
|
||||
assert.ok(!log.some((zeile) => zeile.includes('disabledReason FEHLT')))
|
||||
|
||||
const alle = mockClient({ attrs: KEYS })
|
||||
const log2 = []
|
||||
const heil = await main({ check: true, client: alle, log: (zeile) => log2.push(zeile) })
|
||||
assert.equal(heil.missing.length, 0)
|
||||
assert.ok(!log2.some((zeile) => zeile.includes('FEHLT')))
|
||||
assert.deepEqual(alle.calls, KEINE)
|
||||
})
|
||||
|
||||
test('schemaKeys(portalFeatures) enthaelt die drei Schluessel; der Analyse-Aufbau bleibt fehlerfrei', async () => {
|
||||
const keys = schemaKeys('portalFeatures')
|
||||
for (const key of [...KEYS, 'disabledAt', 'state', 'consentText']) assert.ok(keys.includes(key), key)
|
||||
assert.equal(new Set(keys).size, keys.length, 'keine doppelten Schluessel')
|
||||
|
||||
// Der Aufbau aus analyse.test.js: [...schemaKeys('portalFeatures'), ...ANALYSE_KEYS]
|
||||
const analyse = ANALYSE_ATTRIBUTES.map((a) => a.key)
|
||||
const zusammen = [...keys, ...analyse]
|
||||
// activatedAt steht schon im Grundschema und in ANALYSE_KEYS (harmlos); die neuen Schluessel gehoeren nur einer Seite
|
||||
for (const key of KEYS) assert.ok(!analyse.includes(key), `${key} ist kein Analyse-Attribut`)
|
||||
schema.portalFeatures.push(...zusammen)
|
||||
const { createDocument, updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
const doc = await createDocument(config.collections.portalFeatures, { customerId: 'c1', disabledReason: 'team', accentColor: '#111111' }, 'f_schema')
|
||||
const neu = await updateDocument(config.collections.portalFeatures, doc.$id, { aboEndeMailAt: null, zahlungMeldung: '', offSince: null })
|
||||
assert.equal(neu.disabledReason, 'team')
|
||||
await assert.rejects(() => updateDocument(config.collections.portalFeatures, doc.$id, { gibtEsNicht: 1 }), /Unknown attribute/)
|
||||
})
|
||||
Reference in new issue
Block a user