feat: add http smoke tests.

This commit is contained in:
killian-larcher
2026-06-19 12:19:57 +02:00
parent f2b7e4bfff
commit 456278d2d9
8 changed files with 3374 additions and 2 deletions
+1
View File
@@ -72,6 +72,7 @@ AUTH_PASSKEY_ENABLED=true
# Retention
RETENTION_CRON="* * * * *"
AUDIT_LOG_RETENTION_DAYS=180
TRUSTED_DOMAINS="http://localhost:8887, http://localhost:3055, http://localhost:3056"
+3 -1
View File
@@ -18,6 +18,7 @@ import * as backupStorage from "@/db/schema/14_storage-backup";
import * as healthcheckLog from "@/db/schema/15_healthcheck-log";
import * as apiKey from "@/db/schema/16_apikey";
import * as jobLog from "@/db/schema/17_job-log";
import * as auditEvent from "@/db/schema/18_audit-event";
const log = logger.child({module: "db"});
@@ -55,7 +56,8 @@ export const schemas = {
...backupStorage,
...healthcheckLog,
...apiKey,
...jobLog
...jobLog,
...auditEvent,
};
export const db = drizzle({
+28
View File
@@ -0,0 +1,28 @@
CREATE TYPE "public"."audit_actor_type" AS ENUM('user', 'api_key', 'agent', 'system');--> statement-breakpoint
CREATE TYPE "public"."audit_outcome" AS ENUM('success', 'failure', 'denied');--> statement-breakpoint
CREATE TABLE "audit_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"event_type" varchar(128) NOT NULL,
"category" varchar(64) NOT NULL,
"outcome" "audit_outcome" NOT NULL,
"actor_type" "audit_actor_type" NOT NULL,
"actor_id" uuid,
"actor_name" text,
"actor_api_key_id" uuid,
"actor_api_key_name" text,
"organization_id" uuid,
"organization_name" text,
"target_type" varchar(64),
"target_id" uuid,
"target_name" text,
"ip_address" "inet",
"user_agent" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL
);
--> statement-breakpoint
CREATE INDEX "audit_events_created_at_idx" ON "audit_events" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "audit_events_organization_created_at_idx" ON "audit_events" USING btree ("organization_id","created_at");--> statement-breakpoint
CREATE INDEX "audit_events_category_created_at_idx" ON "audit_events" USING btree ("category","created_at");--> statement-breakpoint
CREATE INDEX "audit_events_event_type_created_at_idx" ON "audit_events" USING btree ("event_type","created_at");--> statement-breakpoint
CREATE INDEX "audit_events_outcome_created_at_idx" ON "audit_events" USING btree ("outcome","created_at");
File diff suppressed because it is too large Load Diff
+7
View File
@@ -435,6 +435,13 @@
"when": 1780822414802,
"tag": "0061_illegal_mole_man",
"breakpoints": true
},
{
"idx": 62,
"version": "7",
"when": 1781507606925,
"tag": "0062_old_quasimodo",
"breakpoints": true
}
]
}
+64
View File
@@ -0,0 +1,64 @@
import { sql } from "drizzle-orm";
import {
index,
inet,
jsonb,
pgEnum,
pgTable,
text,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const auditActorTypeEnum = pgEnum("audit_actor_type", [
"user",
"api_key",
"agent",
"system",
]);
export const auditOutcomeEnum = pgEnum("audit_outcome", [
"success",
"failure",
"denied",
]);
export const auditEvent = pgTable(
"audit_events",
{
id: uuid("id").defaultRandom().primaryKey(),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
eventType: varchar("event_type", { length: 128 }).notNull(),
category: varchar("category", { length: 64 }).notNull(),
outcome: auditOutcomeEnum("outcome").notNull(),
actorType: auditActorTypeEnum("actor_type").notNull(),
actorId: uuid("actor_id"),
actorName: text("actor_name"),
actorApiKeyId: uuid("actor_api_key_id"),
actorApiKeyName: text("actor_api_key_name"),
organizationId: uuid("organization_id"),
organizationName: text("organization_name"),
targetType: varchar("target_type", { length: 64 }),
targetId: uuid("target_id"),
targetName: text("target_name"),
ipAddress: inet("ip_address"),
userAgent: text("user_agent"),
metadata: jsonb("metadata")
.$type<Record<string, unknown>>()
.default(sql`'{}'::jsonb`)
.notNull(),
},
(table) => [
index("audit_events_created_at_idx").on(table.createdAt),
index("audit_events_organization_created_at_idx").on(table.organizationId, table.createdAt),
index("audit_events_category_created_at_idx").on(table.category, table.createdAt),
index("audit_events_event_type_created_at_idx").on(table.eventType, table.createdAt),
index("audit_events_outcome_created_at_idx").on(table.outcome, table.createdAt),
],
);
export const auditEventSchema = createSelectSchema(auditEvent);
export type AuditEvent = z.infer<typeof auditEventSchema>;
+3 -1
View File
@@ -52,6 +52,8 @@ export const env = createEnv({
process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *",
),
AUDIT_LOG_RETENTION_DAYS: z.coerce.number().int().default(180),
CLEANING_HEALTHCHECK_LOGS_CRON: z
.string()
.default(
@@ -144,6 +146,7 @@ export const env = createEnv({
SMTP_SECURE: process.env.SMTP_SECURE,
RETENTION_CRON: process.env.RETENTION_CRON,
AUDIT_LOG_RETENTION_DAYS: process.env.AUDIT_LOG_RETENTION_DAYS,
CLEANING_HEALTHCHECK_LOGS_CRON: process.env.CLEANING_HEALTHCHECK_LOGS_CRON,
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
@@ -200,4 +203,3 @@ export const env = createEnv({
},
});
+56
View File
@@ -0,0 +1,56 @@
import {
auditEventEnrichmentSchema, type AuditEventEnrichment, type WithAuditEventConfigBase, withAuditEventConfigSchema,
} from "@/lib/audit/schema";
import {createAuditEvent} from "@/lib/audit/create-audit-event";
type WithAuditEventCallback<TResult> = (value: TResult | unknown) => AuditEventEnrichment | void;
type WithAuditEventResult = { success: boolean };
export type WithAuditEventConfig<TResult extends WithAuditEventResult> = WithAuditEventConfigBase & {
onSuccess?: (result: TResult) => AuditEventEnrichment | void;
onFailure?: WithAuditEventCallback<TResult>;
};
export async function withAuditEvent<TResult extends WithAuditEventResult>(
effect: () => Promise<TResult>,
config: WithAuditEventConfig<TResult>,
) {
const {onSuccess, onFailure, ...rawConfig} = config;
const parsedConfig = withAuditEventConfigSchema.parse(rawConfig);
const writeOutcome = async (
outcome: "success" | "failure" | "denied",
enrichmentValue: AuditEventEnrichment | void,
) => {
const enrichment = enrichmentValue ? auditEventEnrichmentSchema.parse(enrichmentValue) : {};
await createAuditEvent({
eventType: parsedConfig.eventType,
outcome,
actor: parsedConfig.actor,
organization: enrichment?.organization === undefined ? parsedConfig.organization ?? null : enrichment.organization ?? null,
target: enrichment?.target === undefined ? parsedConfig.target ?? null : enrichment.target ?? null,
ipAddress: parsedConfig.ipAddress ?? null,
userAgent: parsedConfig.userAgent ?? null,
metadata: {...parsedConfig.metadata, ...enrichment.metadata},
});
};
try {
const result = await effect();
if (!result.success) {
await writeOutcome("failure", onFailure?.(result));
return result;
}
await writeOutcome("success", onSuccess?.(result));
return result;
} catch (error) {
await writeOutcome("failure", onFailure?.(error));
throw error;
}
}