optimized events
Test and publish container images / test (push) Successful in 2m26s
Test and publish container images / publish (push) Failing after 3s

This commit is contained in:
BuTzZ
2026-07-28 14:36:47 +02:00
parent b98ec8bc6f
commit 593dbd5f85
42 changed files with 2405 additions and 283 deletions
+203 -1
View File
@@ -3,6 +3,10 @@ import type { Coordinate, FairwayGraph } from "@watermaps/shared";
import { buildServer } from "../src/app.js";
import { loadEnv } from "../src/env.js";
import { createCache } from "../src/services/cache.js";
import {
type FeatureDatabase,
FeatureService
} from "../src/services/features.js";
import type {
LockOperationInfo,
NavigationDataAdapter
@@ -90,11 +94,209 @@ describe("Watermaps API", () => {
});
it("returns app config with map layers", async () => {
const app = await buildServer({ cache: createCache() });
const app = await buildServer({ cache: createCache(), env: productionEnv });
const response = await app.inject({ method: "GET", url: "/api/config" });
expect(response.statusCode).toBe(200);
expect(response.json().layers).toHaveLength(3);
expect(response.json().featureFlags).toMatchObject({
liveFairwayExtraction: false,
postgisFeatures: false,
bridgeAndDepthOverlays: false,
lockAndHarbourContacts: false,
routeEvents: false
});
await app.close();
});
it("advertises feature and contact capabilities only for a configured PostGIS source", async () => {
const pool = {
query: vi.fn().mockImplementation((sql: unknown) =>
Promise.resolve(
String(sql).includes("to_regclass")
? {
rows: [
{
marine_features: "marine_features",
marine_fairway_edges: "marine_fairway_edges"
}
]
}
: {
rows: [{ harbours: true, locks: true, bridges: true }]
}
)
),
end: vi.fn().mockResolvedValue(undefined)
} as unknown as FeatureDatabase;
const featureService = new FeatureService(
{ databaseUrl: "postgres://features.test/watermaps", demoData: false },
{ pool }
);
const app = await buildServer({
cache: createCache(),
env: { ...productionEnv, liveOsmFairways: true },
featureService
});
const response = await app.inject({ method: "GET", url: "/api/config" });
expect(response.statusCode).toBe(200);
expect(response.json().featureFlags).toMatchObject({
liveFairwayExtraction: true,
postgisFeatures: true,
bridgeAndDepthOverlays: true,
lockAndHarbourContacts: true,
routeEvents: true
});
await app.close();
});
it("keeps route-event capabilities disabled until all event layers contain data", async () => {
const pool = {
query: vi.fn().mockImplementation((sql: unknown) =>
Promise.resolve(
String(sql).includes("to_regclass")
? {
rows: [
{
marine_features: "marine_features",
marine_fairway_edges: "marine_fairway_edges"
}
]
}
: {
rows: [{ harbours: false, locks: false, bridges: false }]
}
)
),
end: vi.fn().mockResolvedValue(undefined)
} as unknown as FeatureDatabase;
const featureService = new FeatureService(
{ databaseUrl: "postgres://features.test/watermaps", demoData: false },
{ pool }
);
const app = await buildServer({
cache: createCache(),
env: productionEnv,
featureService
});
const config = await app.inject({ method: "GET", url: "/api/config" });
const readiness = await app.inject({ method: "GET", url: "/ready" });
expect(config.statusCode).toBe(200);
expect(config.json().featureFlags).toMatchObject({
postgisFeatures: false,
bridgeAndDepthOverlays: false,
lockAndHarbourContacts: false,
routeEvents: false
});
expect(readiness.statusCode).toBe(503);
expect(readiness.json().checks.features).toEqual({
ready: false,
source: "postgis",
detail: "data_missing",
missingLayers: ["harbours", "locks", "bridges"]
});
await app.close();
});
it("does not advertise PostGIS capabilities when the configured database is unreachable", async () => {
const pool = {
query: vi.fn().mockRejectedValue(new Error("connection refused")),
end: vi.fn().mockResolvedValue(undefined)
} as unknown as FeatureDatabase;
const featureService = new FeatureService(
{ databaseUrl: "postgres://features.test/watermaps", demoData: false },
{ pool }
);
const app = await buildServer({
cache: createCache(),
env: productionEnv,
featureService
});
const config = await app.inject({ method: "GET", url: "/api/config" });
const readiness = await app.inject({ method: "GET", url: "/ready" });
expect(config.statusCode).toBe(200);
expect(config.json().featureFlags).toMatchObject({
postgisFeatures: false,
bridgeAndDepthOverlays: false,
lockAndHarbourContacts: false,
routeEvents: false
});
expect(readiness.statusCode).toBe(503);
expect(readiness.json().checks.features).toEqual({
ready: false,
source: "postgis",
detail: "database_unreachable"
});
await app.close();
});
it("returns 503 when no feature source is configured", async () => {
const app = await buildServer({ cache: createCache(), env: productionEnv });
const response = await app.inject({
method: "GET",
url: "/api/features?bbox=7.1,53.3,7.5,53.5&layers=harbours,locks,bridges"
});
expect(response.statusCode).toBe(503);
expect(response.json()).toEqual({
error: "feature_source_unavailable",
message: "Keine lokale Karten- und Ereignisdatenbank konfiguriert."
});
await app.close();
});
it("returns 503 when the configured feature database query fails", async () => {
const pool = {
query: vi.fn().mockRejectedValue(new Error("database unavailable")),
end: vi.fn().mockResolvedValue(undefined)
} as unknown as FeatureDatabase;
const featureService = new FeatureService(
{ databaseUrl: "postgres://features.test/watermaps", demoData: false },
{ pool }
);
const app = await buildServer({
cache: createCache(),
env: productionEnv,
featureService
});
const response = await app.inject({
method: "GET",
url: "/api/features?bbox=7.1,53.3,7.5,53.5&layers=harbours,locks,bridges"
});
expect(response.statusCode).toBe(503);
expect(response.json()).toEqual({
error: "feature_source_unavailable",
message: "Die lokale Karten- und Ereignisdatenbank ist momentan nicht erreichbar."
});
await app.close();
});
it("separates liveness from feature-data readiness", async () => {
const app = await buildServer({ cache: createCache(), env: productionEnv });
const health = await app.inject({ method: "GET", url: "/health" });
const readiness = await app.inject({ method: "GET", url: "/ready" });
expect(health.statusCode).toBe(200);
expect(health.json()).toMatchObject({
ok: true,
featureSource: "unavailable"
});
expect(readiness.statusCode).toBe(503);
expect(readiness.json()).toEqual({
ok: false,
service: "watermaps-api",
checks: {
features: {
ready: false,
source: "unavailable",
detail: "not_configured"
}
}
});
await app.close();
});