Restrict tenant imports to new tenants

This commit is contained in:
2026-08-06 15:19:11 +02:00
parent c66d04f0b5
commit cb37970074
12 changed files with 38 additions and 1867 deletions

View File

@@ -1,52 +0,0 @@
import assert from "node:assert/strict"
import test from "node:test"
import { addTenantExportGlobalResources } from "../src/utils/tenantExportGlobalResources"
test("adds global resources and the tenant account chart to a tenant export", async () => {
const tables: Record<string, Record<string, any>[]> = {}
const calls: { table: string, whereSql: string, params?: any[] }[] = []
const rowsByTable: Record<string, Record<string, any>[]> = {
units: [{ id: 1, name: "Monat" }],
citys: [{ id: 1, zip: 26316 }],
countrys: [{ id: 1, name: "Deutschland" }],
accounts: [{ id: 1, accountChart: "skr04", number: "4400" }],
}
await addTenantExportGlobalResources(
tables,
new Set(Object.keys(rowsByTable)),
{ id: 42, accountChart: "skr04" },
async (table, whereSql, params) => {
calls.push({ table, whereSql, params })
return rowsByTable[table]
},
(target, table, rows) => {
target[table] = rows
}
)
assert.deepEqual(tables, rowsByTable)
assert.deepEqual(calls, [
{ table: "units", whereSql: "true", params: undefined },
{ table: "citys", whereSql: "true", params: undefined },
{ table: "countrys", whereSql: "true", params: undefined },
{ table: "accounts", whereSql: `"accountChart" = $1`, params: ["skr04"] },
])
})
test("skips global tables that do not exist in an older database schema", async () => {
const tables: Record<string, Record<string, any>[]> = {}
await addTenantExportGlobalResources(
tables,
new Set(["units"]),
{ id: 42 },
async () => [{ id: 1, name: "Stück" }],
(target, table, rows) => {
target[table] = rows
}
)
assert.deepEqual(tables, { units: [{ id: 1, name: "Stück" }] })
})

View File

@@ -1,48 +0,0 @@
import assert from "node:assert/strict"
import test from "node:test"
import { restoreImportedTenantNumberRanges } from "../src/utils/tenantImportNumberRanges"
test("restores number ranges when importing into an existing target tenant", async () => {
const queries: { query: string, values: unknown[] }[] = []
const numberRanges = {
invoices: { prefix: "RE-", suffix: "", nextNumber: 4712 },
}
const client = {
async query(query: string, values: unknown[]) {
queries.push({ query, values })
return { rowCount: 1 }
},
}
const updated = await restoreImportedTenantNumberRanges(client, {
tenantId: 42,
tables: {
tenants: [{ id: 42, name: "Zieltenant", numberRanges }],
},
})
assert.equal(updated, 1)
assert.deepEqual(queries, [{
query: `update "tenants" set "numberRanges" = $1::jsonb where "id" = $2`,
values: [JSON.stringify(numberRanges), 42],
}])
})
test("does not overwrite number ranges when the export contains none", async () => {
let queryCalled = false
const client = {
async query() {
queryCalled = true
return { rowCount: 1 }
},
}
const updated = await restoreImportedTenantNumberRanges(client, {
tenantId: 42,
tables: { tenants: [{ id: 42, name: "Zieltenant" }] },
})
assert.equal(updated, 0)
assert.equal(queryCalled, false)
})

View File

@@ -1,68 +0,0 @@
import assert from "node:assert/strict"
import test from "node:test"
import { buildTenantMergePlan } from "../src/utils/tenantMergePlan"
test("classifies imports, existing rows, conflicts and global id collisions", () => {
const plan = buildTenantMergePlan({
units: [
{ id: 1, name: "Stück", short: "Stk." },
{ id: 2, name: "Monat", short: "Mon." },
{ id: 3, name: "Stunde", short: "Std." },
],
customers: [
{ id: 10, tenant: 42, name: "Neu" },
{ id: 11, tenant: 42, name: "Geändert" },
],
}, {
units: [
{ id: 1, name: "Stück", short: "Stk.", updated_at: "later" },
{ id: 2, name: "Kilometer", short: "km" },
{ id: 9, name: "Stunde", short: "h" },
],
customers: [
{ id: 11, tenant: 42, name: "Zieländerung" },
],
}, {
units: { primaryKey: ["id"] },
customers: { primaryKey: ["id"] },
})
assert.deepEqual(plan.summary, {
import: 1,
existing: 1,
conflict: 2,
id_collision: 1,
})
assert.equal(plan.items.find((item) => item.label === "Monat")?.kind, "id_collision")
assert.equal(plan.items.find((item) => item.label === "Stunde")?.kind, "conflict")
assert.equal(plan.items.find((item) => item.label === "Geändert")?.defaultDecision, "target")
})
test("uses target as the safe default for two-way conflicts", () => {
const plan = buildTenantMergePlan({
accounts: [{ id: 5, accountChart: "skr03", number: "8400", label: "Quelle" }],
}, {
accounts: [{ id: 99, accountChart: "skr03", number: "8400", label: "Ziel" }],
}, {
accounts: { primaryKey: ["id"] },
})
assert.equal(plan.items[0].kind, "conflict")
assert.equal(plan.items[0].targetKey, "id=99")
assert.equal(plan.items[0].defaultDecision, "target")
assert.deepEqual(plan.items[0].differences.sort(), ["id", "label"])
})
test("ignores maintenance locks and audit timestamps during comparison", () => {
const plan = buildTenantMergePlan({
tenants: [{ id: 42, name: "Tenant", locked: null, updatedAt: "before" }],
}, {
tenants: [{ id: 42, name: "Tenant", locked: "maintenance_tenant", updatedAt: "after" }],
}, {
tenants: { primaryKey: ["id"] },
})
assert.equal(plan.items[0].kind, "existing")
})