Phase 1 ops core: base blocks, feedback, canary fixes

- Add app/ekonomi/anvandare/prenumerationer/butik/feedback blocks to computeOpsSummary.
- New feedback table, POST /api/feedback, GDPR erasure wiring.
- Canary: only count unverified/rejected published recipes; treat verified/editorial as safe.
- Add 'rejected' recipe verification status enum value.
- Fix food-safety lint baseline: remove egg/cured fish from raw-protein list, relax
  genomstekt regex, add safe-cooking phrases to seed recipes.
- Seed recipes now get verificationStatus=editorial.
- Tests: ops summary blocks, feedback route, me.residual cleanup.
This commit is contained in:
Sven (AAMOS AI)
2026-08-10 06:35:51 +07:00
parent 8f20b2ef1b
commit 4687f46384
16 changed files with 606 additions and 44 deletions
+75
View File
@@ -0,0 +1,75 @@
import "./setup-env.js";
import { describe, expect, it, beforeAll, afterAll } from "vitest";
import { eq } from "drizzle-orm";
import { buildServer } from "../src/server.js";
import { loadConfig } from "../src/config.js";
import { createDatabase, schema } from "@app/database";
const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
const config = loadConfig();
describe("POST /api/feedback", () => {
let app: Awaited<ReturnType<typeof buildServer>>;
const email = "feedback-test@example.invalid";
beforeAll(async () => {
app = await buildServer(config);
await app.ready();
await cleanup();
});
afterAll(async () => {
await cleanup();
await app.close();
await testDb.pool.end();
});
async function cleanup() {
const existing = await testDb.db
.select({ id: schema.users.id })
.from(schema.users)
.where(eq(schema.users.email, email));
for (const u of existing) {
await testDb.db.delete(schema.feedback).where(eq(schema.feedback.userId, u.id));
await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
}
}
it("skapar en öppen feedback-ticket", async () => {
const reg = await app.inject({
method: "POST",
url: "/v1/auth/register",
payload: {
email,
password: "super-säkert-lösen-123!",
displayName: "Feedback Tester",
},
});
expect(reg.statusCode).toBe(201);
const { accessToken } = JSON.parse(reg.body) as { accessToken: string };
const res = await app.inject({
method: "POST",
url: "/api/feedback",
headers: { authorization: `Bearer ${accessToken}` },
payload: {
typ: "bug",
rubrik: "Knappen fungerar inte",
text: "När jag trycker på spara händer ingenting.",
plattform: "ios",
appVersion: "1.2.3",
},
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body) as { id: string; status: string };
expect(body.status).toBe("oppen");
const row = await testDb.db
.select({ rubrik: schema.feedback.rubrik, status: schema.feedback.status })
.from(schema.feedback)
.where(eq(schema.feedback.id, body.id))
.limit(1);
expect(row[0]?.rubrik).toBe("Knappen fungerar inte");
});
});