feat: api v1 garden bangs routes and openapi

This commit is contained in:
ginnoir
2026-07-04 19:13:49 -05:00
parent d4304b005c
commit e8d13bede8
16 changed files with 1351 additions and 113 deletions
+83
View File
@@ -0,0 +1,83 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { resolveApiAuth } from "../../src/lib/api-auth";
describe("resolveApiAuth", () => {
it("returns null without session or bearer token", async () => {
const request = new Request("http://localhost/api/v1/garden/plants");
const ctx = await resolveApiAuth(request);
assert.equal(ctx, null);
});
it("returns null for malformed bearer header", async () => {
const request = new Request("http://localhost/api/v1/garden/plants", {
headers: { Authorization: "Basic dXNlcjpwYXNz" },
});
assert.equal(await resolveApiAuth(request), null);
});
it("returns null for empty bearer token without hitting DB match", async () => {
const request = new Request("http://localhost/api/v1/garden/plants", {
headers: { Authorization: "Bearer " },
});
assert.equal(await resolveApiAuth(request), null);
});
it("accepts Bearer prefix on Authorization header shape", async () => {
const request = new Request("http://localhost/api/v1/garden/plants", {
headers: { Authorization: "Bearer famapp-api-token-abc123" },
});
const header = request.headers.get("Authorization");
assert.ok(header?.startsWith("Bearer "));
assert.equal(header!.slice("Bearer ".length).trim(), "famapp-api-token-abc123");
});
});
describe("GET /api/v1/garden/plants auth gate", () => {
it("returns 401 without authentication", async () => {
const { GET } = await import("../../src/app/api/v1/garden/plants/route");
const request = new Request("http://localhost/api/v1/garden/plants");
const response = await GET(request);
assert.equal(response.status, 401);
assert.deepEqual(await response.json(), { error: "Unauthorized" });
});
});
describe("GET /api/v1/garden/containers auth gate", () => {
it("returns 401 without authentication", async () => {
const { GET } = await import("../../src/app/api/v1/garden/containers/route");
const request = new Request("http://localhost/api/v1/garden/containers");
const response = await GET(request);
assert.equal(response.status, 401);
assert.deepEqual(await response.json(), { error: "Unauthorized" });
});
});
describe("GET /api/v1/bangs auth gate", () => {
it("returns 401 without authentication", async () => {
const { GET } = await import("../../src/app/api/v1/bangs/route");
const request = new Request("http://localhost/api/v1/bangs");
const response = await GET(request);
assert.equal(response.status, 401);
assert.deepEqual(await response.json(), { error: "Unauthorized" });
});
});
describe("PATCH /api/v1/bangs/[id] auth gate", () => {
it("returns 401 without authentication", async () => {
const { PATCH } = await import("../../src/app/api/v1/bangs/[id]/route");
const request = new Request(
"http://localhost/api/v1/bangs/00000000-0000-4000-8000-000000000001",
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ occurredOn: "2026-01-01" }),
},
);
const response = await PATCH(request, {
params: Promise.resolve({ id: "00000000-0000-4000-8000-000000000001" }),
});
assert.equal(response.status, 401);
assert.deepEqual(await response.json(), { error: "Unauthorized" });
});
});