Skip to content

Code Cleanup: Duplikate + Extraktionen

Prioritaet 1: Wird durch geplante Features geloest

Diese Duplikate verschwinden automatisch wenn die geplanten Features gebaut werden:

DuplikatWie oftGeloest durch
ctx["db"] as DbConnection17xTenant DB Contextctx.db typisiert
Test Setup (createTestDb + createTestRedis + cleanup)6+ DateienTestingcreateTestApp()
Test SessionUser Mocks9+ DateienTestingapp.asUser() + Factories
Handler Registration BoilerplateAlle FeaturesFeature-StrukturdefineWriteHandler Objekte

Prioritaet 2: Jetzt extrahieren (vor neuen Features)

2a: Test Request Helper → shared Utility

Aktuell 5 verschiedene Varianten von req():

// VORHER: in jeder Testdatei kopiert
async function req(method: string, path: string, user: SessionUser, body?: unknown) {
const token = await jwt.sign(user);
const init: RequestInit = { method, headers: { ... } };
if (body) init.body = JSON.stringify(body);
return app.request(path, init);
}
packages/framework/src/testing/request-helper.ts
// NACHHER: shared utility
export function createRequestHelper(app: HonoApp, jwt: JwtHelper) {
return {
write: (type: string, payload: unknown, user: SessionUser) => ...,
query: (type: string, payload: unknown, user: SessionUser) => ...,
command: (type: string, payload: unknown, user: SessionUser) => ...,
};
}

Dateien: full-stack.integration.ts, field-access.integration.ts, jobs-feature.integration.ts, multi-tenant.integration.ts, files.integration.ts

2b: Test Assertions → shared Helper

if (!result.isSuccess) 25x in Tests:

// VORHER:
const result = await write("user.create", payload);
if (!result.isSuccess) throw new Error("Setup failed");
// NACHHER:
// packages/framework/src/testing/assertions.ts
export function expectSuccess<T>(result: WriteResult<T>): asserts result is { isSuccess: true; data: T } {
if (!result.isSuccess) {
throw new Error(`Expected success but got error: ${result.error}`);
}
}
// Nutzung:
const result = await write("user.create", payload);
expectSuccess(result);
// result.data ist jetzt typisiert

2c: sleep() → shared Utility

// VORHER: 3x kopiert
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// NACHHER:
// packages/framework/src/testing/utils.ts
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

2d: SessionUser Fixtures

// VORHER: 9x kopiert
const adminUser: SessionUser = { id: 1, tenantId: 1, roles: ["Admin"] };
const normalUser: SessionUser = { id: 2, tenantId: 1, roles: ["User"] };
// NACHHER:
// packages/framework/src/testing/fixtures.ts
export const TestUsers = {
admin: { id: 1, tenantId: 1, roles: ["Admin"] },
systemAdmin: { id: 1, tenantId: 1, roles: ["SystemAdmin"] },
user: { id: 2, tenantId: 1, roles: ["User"] },
driver: { id: 3, tenantId: 1, roles: ["Driver"] },
otherTenant: { id: 10, tenantId: 2, roles: ["Admin"] },
} as const satisfies Record<string, SessionUser>;

2e: Standard Table SQL → Table Builder nutzen

Aktuell hardcoded SQL in 4+ Test-Dateien:

-- VORHER: manuell in jeder Testdatei
CREATE TABLE "test_Users" (
"id" SERIAL PRIMARY KEY,
"tenant_id" INTEGER NOT NULL,
"version" INTEGER NOT NULL DEFAULT 1,
"inserted_at" TIMESTAMP NOT NULL DEFAULT NOW(),
...
);
// NACHHER: Table Builder generiert
const table = buildTable("test_Users", entityDef);
await db.execute(table.createSQL());

2f: Buffer/Encoding → shared Utility

// VORHER: verstreut in cursor.ts, encryption.ts, files
Buffer.from(value).toString("base64")
Buffer.from(value, "base64").toString("utf-8")
// NACHHER:
// packages/framework/src/utils/encoding.ts
export const encode = {
toBase64: (value: string) => Buffer.from(value).toString("base64"),
fromBase64: (value: string) => Buffer.from(value, "base64").toString("utf-8"),
toJson: <T>(value: T) => JSON.stringify(value),
fromJson: <T>(value: string) => JSON.parse(value) as T,
};

2g: JSON Roles Parsing → shared Helper

// VORHER: kopiert in tenant + tests
roles: JSON.parse(row["roles"] as string) as string[]
// NACHHER:
// packages/framework/src/utils/serialization.ts
export function parseRoles(raw: unknown): string[] {
if (typeof raw === "string") return JSON.parse(raw);
if (Array.isArray(raw)) return raw;
return [];
}

Zusammenfassung

StepWasDateien betroffen
2aTest Request Helper5 Testdateien
2bTest Assertions (expectSuccess)25+ Stellen
2csleep()3 Testdateien
2dSessionUser Fixtures9 Testdateien
2eTable SQL → Builder4 Testdateien
2fBuffer/Encoding4 Dateien
2gJSON Roles Parsing3 Dateien

Reihenfolge: 2a → 2d → 2b → 2c (Test Utilities zuerst, dann nutzt jeder Test sie). 2e-2g koennen parallel.

Neue Dateistruktur Testing

packages/framework/src/testing/
index.ts ← Re-exports
create-test-app.ts ← createTestApp() (spaeter, aus Testing Plan)
create-test-db.ts ← Schon da
create-test-redis.ts ← Schon da
request-helper.ts ← NEU: write(), query(), command()
assertions.ts ← NEU: expectSuccess(), expectError()
fixtures.ts ← NEU: TestUsers, TestTenants
utils.ts ← NEU: sleep()
factories.ts ← Spaeter: Entity Factories