API-first: OpenAPI 3.0-spec för plattforms-API:t
- services/plattform/openapi.yaml dokumenterar hela ytan: auth (registrera organisation, logga in), användarhantering (admin), ärenden + append-only händelselogg (idempotent synk), arbetsledar- översikten, publik Live Share-delning och AI-orkestern — inklusive scheman för alla 15 händelsetyper, roller, JWT-anspråken och API:ts bärande principer (append-only, multi-tenant-404). - Specen serveras live av plattformstjänsten på GET /api/openapi.yaml och följer med i containern. - Verifierad i tre lager: maskinell validering (swagger-cli), paritetstest i vitest (varje dokumenterad väg finns i servern, händelsetyperna är kompletta) och integrationsteststeg som hämtar specen från den körande tjänsten. Verifierat: integrationstestets 18 kontroller gröna mot Postgres 16, 29 vitest-tester gröna, produktionsbygge ok. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
+2
-1
@@ -50,7 +50,8 @@ kubectl apply -k infra/k8s
|
||||
|
||||
# 4. Verifiera
|
||||
kubectl -n guidad-felsokning get pods
|
||||
curl https://app.exempel.se/halsa # → {"status":"ok"} (plattformen)
|
||||
curl https://app.exempel.se/halsa # → {"status":"ok"} (plattformen)
|
||||
curl https://app.exempel.se/api/openapi.yaml # API-first: hela API-specen
|
||||
```
|
||||
|
||||
Byt domän och cert-issuer i `infra/k8s/ingress.yaml`. Att skapa nya organisationer är öppet i beta — stäng med `REGISTRERING_OPPEN=false` på plattformens Deployment; användare inom en organisation skapas alltid av dess systemadministratör.
|
||||
|
||||
+1
-2
@@ -33,7 +33,7 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st
|
||||
| Live Share | ✅ Skrivskyddad livevy per ärende (`/felsokning/dela/:id`): status ✔/🔄/⏳, bilder, mätvärdestabell, tidslinje, rekommenderat nästa steg. Uppdateras automatiskt, interna poster filtreras bort. Publik delningssida (`/felsokning/delad/:kod`) läser via `hamta_delat_arende` utan inloggning och pollar för liveuppdatering; "Kopiera delningslänk" finns i rapportfliken. Behörighetsnivåer (kund/intern/partner) i nästa fas. |
|
||||
| Dashboard | ✅ Enligt direktivet: räknare och filter för Alla/Pågående/Klara plus Starta nytt ärende. |
|
||||
| Utskrift | ✅ Kundrapport och Live Share-vy skrivs ut svart på vitt; interaktiva element döljs automatiskt. |
|
||||
| Öppet API | 🔶 Datamodellen är API-klar (händelser som JSON i Postgres). REST/OpenAPI-lager i nästa fas. |
|
||||
| Öppet API | ✅ Plattforms-API:t är dokumenterat med OpenAPI 3.0 (`services/plattform/openapi.yaml`) — auth, användare, ärenden/händelser (append-only), översikt, publik delning och AI-orkestern, med scheman för alla händelsetyper. Specen valideras maskinellt, paritetstestas mot serverns rutter och serveras live på `GET /api/openapi.yaml`. |
|
||||
|
||||
## Arkitekturprinciper i koden
|
||||
|
||||
@@ -47,4 +47,3 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st
|
||||
- Omfördelning av ärenden (arbetsledare) är inte implementerad — överlämningshändelsen täcker handover tills en ansvarig-modell införs.
|
||||
- QR/streckkod/OCR för objektidentifiering, bildanalys och tillverkarintegrationer ingår inte ännu.
|
||||
- Live Share-behörighetsnivåerna (kund/intern/partner) är specificerade men inte byggda — dagens delningslänk motsvarar kundnivån.
|
||||
- OpenAPI-dokumentation av plattforms-API:t återstår (API-first-kravet).
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install --omit=dev --no-audit --no-fund && npm cache clean --force
|
||||
COPY server.mjs ./
|
||||
COPY server.mjs openapi.yaml ./
|
||||
|
||||
ENV NODE_ENV=production PORT=8080
|
||||
USER node
|
||||
|
||||
@@ -112,4 +112,11 @@ kontroll "översikten härleder felbeskrivning" "$(echo "$OVERSIKT" | falt '.are
|
||||
kontroll "översikten härleder status" "$(echo "$OVERSIKT" | falt '.arenden[0].avslutat')" "false"
|
||||
kontroll "översikten räknar händelser" "$(echo "$OVERSIKT" | falt '.arenden[0].antal_handelser')" "2"
|
||||
|
||||
# 10. API-first: OpenAPI-specen serveras live, utan inloggning
|
||||
SPEC=$(curl -s "$BAS/api/openapi.yaml")
|
||||
case "$SPEC" in
|
||||
"openapi: 3.0.3"*) echo "✓ OpenAPI-specen serveras på /api/openapi.yaml" ;;
|
||||
*) echo "✗ OpenAPI-specen saknas"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "Integrationstest: allt grönt"
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Guidad Felsökning – plattforms-API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
API-first-ytan för Guidad Felsökning: autentisering, organisationens
|
||||
användare, ärenden med append-only händelselogg, arbetsledaröversikt,
|
||||
publik Live Share-delning samt AI-orkestern.
|
||||
|
||||
**Händelseloggen är append-only.** Det finns inga update- eller
|
||||
delete-operationer — historik kan aldrig ändras eller raderas, vilket
|
||||
även garanteras av databastriggers. En händelse med redan känt `id`
|
||||
ignoreras tyst (idempotent synk).
|
||||
|
||||
**Multi-tenant.** All ärendedata är organisationsknuten. Ett ärende i
|
||||
en annan organisation ger `404`, som om det inte fanns.
|
||||
|
||||
Specen serveras live av tjänsten på `GET /api/openapi.yaml`.
|
||||
servers:
|
||||
- url: https://app.exempel.se
|
||||
description: Klustrets ingress (ersätt med er domän)
|
||||
tags:
|
||||
- name: Auth
|
||||
- name: Användare
|
||||
- name: Ärenden
|
||||
- name: Översikt
|
||||
- name: Delning
|
||||
- name: AI
|
||||
- name: Drift
|
||||
|
||||
paths:
|
||||
/halsa:
|
||||
get:
|
||||
tags: [Drift]
|
||||
summary: Hälsokontroll
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Tjänsten svarar.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status: { type: string, example: ok }
|
||||
|
||||
/api/openapi.yaml:
|
||||
get:
|
||||
tags: [Drift]
|
||||
summary: Denna specifikation
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: OpenAPI-specen i YAML.
|
||||
content:
|
||||
application/yaml:
|
||||
schema: { type: string }
|
||||
|
||||
/api/auth/registrera:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Skapa organisation + systemadministratör
|
||||
description: >
|
||||
Registrerar en ny organisation (tenant) och dess första användare,
|
||||
som blir systemadministratör. Kan stängas av driften
|
||||
(REGISTRERING_OPPEN=false → 403).
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord, namn, organisation]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string, minLength: 8 }
|
||||
namn: { type: string }
|
||||
organisation: { type: string }
|
||||
responses:
|
||||
"200": { $ref: "#/components/responses/Inloggad" }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/auth/logga-in:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Logga in
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string }
|
||||
responses:
|
||||
"200": { $ref: "#/components/responses/Inloggad" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/anvandare:
|
||||
get:
|
||||
tags: [Användare]
|
||||
summary: Lista organisationens användare
|
||||
description: Kräver rollen `admin`.
|
||||
responses:
|
||||
"200":
|
||||
description: Användare i den egna organisationen.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
anvandare:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Anvandare" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Användare]
|
||||
summary: Skapa användare i den egna organisationen
|
||||
description: Kräver rollen `admin`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord, namn, roll]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string, minLength: 8 }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
responses:
|
||||
"200":
|
||||
description: Den skapade användaren.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Anvandare" }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden:
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Lista organisationens ärenden
|
||||
responses:
|
||||
"200":
|
||||
description: Ärenden (senaste 200).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Arende" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Ärenden]
|
||||
summary: Registrera ett ärende (idempotent)
|
||||
description: >
|
||||
Skapar ärendet i användarens organisation. Ett redan känt `id`
|
||||
ignoreras tyst — ärenderaden ändras aldrig.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id, nummer, skapad]
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodikId: { type: string, nullable: true }
|
||||
responses:
|
||||
"200":
|
||||
description: Mottaget.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden/{arendeId}/handelser:
|
||||
parameters:
|
||||
- name: arendeId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Hämta ärendets händelselogg
|
||||
responses:
|
||||
"200":
|
||||
description: Händelser i tidsordning.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
handelser:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Ärenden]
|
||||
summary: Lägg till händelser (append-only, idempotent)
|
||||
description: >
|
||||
Lägger till händelser i loggen. Max 500 per anrop. En händelse med
|
||||
redan känt `id` ignoreras tyst — befintliga händelser skrivs aldrig
|
||||
över, och databastriggern stoppar alla ändringsförsök.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [handelser]
|
||||
properties:
|
||||
handelser:
|
||||
type: array
|
||||
maxItems: 500
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
responses:
|
||||
"200":
|
||||
description: Mottaget.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/oversikt:
|
||||
get:
|
||||
tags: [Översikt]
|
||||
summary: Organisationsöversikt med status och statistikunderlag
|
||||
description: >
|
||||
Kräver rollen `arbetsledare` eller `admin`. Status, deltagande
|
||||
tekniker och sammanfattning härleds ur händelseloggen.
|
||||
responses:
|
||||
"200":
|
||||
description: Alla ärenden i organisationen med härledd status.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/OversiktsRad" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/delad/{delningskod}:
|
||||
get:
|
||||
tags: [Delning]
|
||||
summary: Publik Live Share-vy via delningskod
|
||||
description: >
|
||||
Kräver ingen inloggning — delningskoden är nyckeln. Interna poster
|
||||
(kategoribyten, hypoteser, AI-dialog) är bortfiltrerade.
|
||||
security: []
|
||||
parameters:
|
||||
- name: delningskod
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, pattern: "^[a-z0-9-]+$" }
|
||||
responses:
|
||||
"200":
|
||||
description: Ärendet med kunddelbar händelselogg.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arende:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
handelser:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/ai:
|
||||
post:
|
||||
tags: [AI]
|
||||
summary: AI-orkestern (separat tjänst bakom samma ingress)
|
||||
description: >
|
||||
Routas av ingressen till ai-orkester-tjänsten. Servern äger
|
||||
modellval, effort, systemprompt och svarsschema per uppgift:
|
||||
handledning (Claude Sonnet 5), granskning (Claude Opus 5),
|
||||
sammanfattning (Claude Sonnet 5), metodikval (Claude Haiku 4.5).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [uppgift, prompt]
|
||||
properties:
|
||||
uppgift:
|
||||
type: string
|
||||
enum: [handledning, granskning, sammanfattning, metodikval]
|
||||
prompt: { type: string, maxLength: 40000 }
|
||||
responses:
|
||||
"200":
|
||||
description: Klassificerat AI-svar med modellen som svarade.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
modell: { type: string, example: claude-sonnet-5 }
|
||||
svar:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/AiSvar"
|
||||
- type: object
|
||||
properties:
|
||||
metodikId:
|
||||
type: string
|
||||
enum: [vibration, elsystem, generisk]
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"502": { $ref: "#/components/responses/Fel" }
|
||||
"503": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: >
|
||||
HS256-JWT från /api/auth/logga-in eller /api/auth/registrera.
|
||||
Anspråk: sub (användar-id), namn, org (organisations-id),
|
||||
roll, iat, exp (12 timmar).
|
||||
responses:
|
||||
Inloggad:
|
||||
description: Inloggad — token + kontouppgifter.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token: { type: string }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
organisation: { type: string }
|
||||
Fel:
|
||||
description: Felsvar.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error: { type: string }
|
||||
schemas:
|
||||
Roll:
|
||||
type: string
|
||||
enum: [tekniker, arbetsledare, admin]
|
||||
Anvandare:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
epost: { type: string, format: email }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
Arende:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodik_id: { type: string, nullable: true }
|
||||
LoggPost:
|
||||
type: object
|
||||
required: [id, tidpunkt, anvandare, handelse]
|
||||
properties:
|
||||
id: { type: string }
|
||||
tidpunkt: { type: string, format: date-time }
|
||||
anvandare: { type: string }
|
||||
handelse: { $ref: "#/components/schemas/Handelse" }
|
||||
Handelse:
|
||||
type: object
|
||||
description: >
|
||||
Diskriminerad på `typ`; fullständiga fältdefinitioner i klientens
|
||||
domänmodell (src/felsokning/domain.ts).
|
||||
required: [typ]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
typ:
|
||||
type: string
|
||||
enum:
|
||||
- objekt_identifierat
|
||||
- felbeskrivning
|
||||
- fraga_besvarad
|
||||
- kontroll_utford
|
||||
- observation
|
||||
- matvarde
|
||||
- hypotes
|
||||
- foto
|
||||
- kommentar
|
||||
- kategori_byte
|
||||
- inaktivitet_forklarad
|
||||
- overlamning
|
||||
- export_skapad
|
||||
- ai_svar
|
||||
- arende_avslutat
|
||||
OversiktsRad:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodik_id: { type: string, nullable: true }
|
||||
antal_handelser: { type: integer }
|
||||
forsta: { type: string, format: date-time, nullable: true }
|
||||
senaste: { type: string, format: date-time, nullable: true }
|
||||
avslutat: { type: boolean }
|
||||
objekt: { type: string, nullable: true }
|
||||
felbeskrivning: { type: string, nullable: true }
|
||||
tekniker:
|
||||
type: array
|
||||
items: { type: string }
|
||||
nullable: true
|
||||
AiSvar:
|
||||
type: object
|
||||
required: [rader, nastaSteg]
|
||||
properties:
|
||||
rader:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [typ, text]
|
||||
properties:
|
||||
typ:
|
||||
type: string
|
||||
enum: [observation, verifierat, hypotes, rekommendation]
|
||||
text: { type: string }
|
||||
nastaSteg: { type: string }
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -16,8 +16,14 @@
|
||||
|
||||
import { createServer } from "node:http";
|
||||
import { createHmac, timingSafeEqual } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import pg from "pg";
|
||||
|
||||
// API-first: OpenAPI-specen är en versionerad artefakt och serveras live.
|
||||
const OPENAPI = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "openapi.yaml"), "utf8");
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 8080);
|
||||
const MAX_KROPP = 4 * 1024 * 1024;
|
||||
const TOKEN_LIVSTID_S = 12 * 60 * 60;
|
||||
@@ -117,6 +123,13 @@ export function skapaServer() {
|
||||
if (req.method === "GET" && vag === "/halsa") {
|
||||
return svara(res, 200, { status: "ok" });
|
||||
}
|
||||
if (req.method === "GET" && vag === "/api/openapi.yaml") {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/yaml; charset=utf-8",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
});
|
||||
return res.end(OPENAPI);
|
||||
}
|
||||
|
||||
const hemlighet = process.env.JWT_SECRET;
|
||||
if (!hemlighet) return svara(res, 503, { error: "Tjänsten är inte konfigurerad." });
|
||||
|
||||
@@ -61,6 +61,33 @@ describe("AI-orkestern", () => {
|
||||
expect(schema).toContain("check (roll in ('tekniker', 'arbetsledare', 'admin'))");
|
||||
});
|
||||
|
||||
it("OpenAPI-specen och plattformsservern täcker samma endpoints", () => {
|
||||
const spec = readFileSync("services/plattform/openapi.yaml", "utf8");
|
||||
const server = readFileSync("services/plattform/server.mjs", "utf8");
|
||||
// Varje dokumenterad väg finns i servern …
|
||||
const vagar: [string, string][] = [
|
||||
["/halsa", "/halsa"],
|
||||
["/api/openapi.yaml", "/api/openapi.yaml"],
|
||||
["/api/auth/registrera", "/api/auth/registrera"],
|
||||
["/api/auth/logga-in", "/api/auth/logga-in"],
|
||||
["/api/anvandare", "/api/anvandare"],
|
||||
["/api/arenden", "/api/arenden"],
|
||||
["/api/arenden/{arendeId}/handelser", "handelser"],
|
||||
["/api/oversikt", "/api/oversikt"],
|
||||
["/api/delad/{delningskod}", "delad"],
|
||||
];
|
||||
for (const [iSpec, iServer] of vagar) {
|
||||
expect(spec).toContain(`${iSpec}:`);
|
||||
expect(server).toContain(iServer);
|
||||
}
|
||||
// … och AI-endpointen + händelsetyperna är dokumenterade.
|
||||
expect(spec).toContain("/api/ai:");
|
||||
expect(spec).toContain("append-only");
|
||||
for (const typ of ["objekt_identifierat", "kontroll_utford", "ai_svar", "arende_avslutat"]) {
|
||||
expect(spec).toContain(typ);
|
||||
}
|
||||
});
|
||||
|
||||
it("endpointen kodar AI-reglerna i grundprompten", () => {
|
||||
expect(ENDPOINT).toContain("Hitta aldrig på fakta");
|
||||
expect(ENDPOINT).toContain("aldrig en hypotes som ett konstaterat fel");
|
||||
|
||||
Reference in New Issue
Block a user