diff --git a/_regroup/test-etapi/metrics.http b/_regroup/test-etapi/metrics.http deleted file mode 100644 index 24435f954..000000000 --- a/_regroup/test-etapi/metrics.http +++ /dev/null @@ -1,82 +0,0 @@ -### Test ETAPI metrics endpoint - -# First login to get a token -POST {{triliumHost}}/etapi/auth/login -Content-Type: application/json - -{ - "password": "{{password}}" -} - -> {% -client.test("Login successful", function() { - client.assert(response.status === 201, "Response status is not 201"); - client.assert(response.body.authToken, "Auth token not present"); - client.global.set("authToken", response.body.authToken); -}); -%} - -### Get metrics with authentication (default Prometheus format) -GET {{triliumHost}}/etapi/metrics -Authorization: {{authToken}} - -> {% -client.test("Metrics endpoint returns Prometheus format by default", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain"); - client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric"); - client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric"); - client.assert(response.body.includes("# HELP"), "Should contain HELP comments"); - client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments"); -}); -%} - -### Get metrics in JSON format -GET {{triliumHost}}/etapi/metrics?format=json -Authorization: {{authToken}} - -> {% -client.test("Metrics endpoint returns JSON when requested", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json"); - client.assert(response.body.version, "Version info not present"); - client.assert(response.body.database, "Database info not present"); - client.assert(response.body.timestamp, "Timestamp not present"); - client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number"); - client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number"); -}); -%} - -### Get metrics in Prometheus format explicitly -GET {{triliumHost}}/etapi/metrics?format=prometheus -Authorization: {{authToken}} - -> {% -client.test("Metrics endpoint returns Prometheus format when requested", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain"); - client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric"); - client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric"); -}); -%} - -### Test invalid format parameter -GET {{triliumHost}}/etapi/metrics?format=xml -Authorization: {{authToken}} - -> {% -client.test("Invalid format parameter returns error", function() { - client.assert(response.status === 400, "Response status should be 400"); - client.assert(response.body.code === "INVALID_FORMAT", "Error code should be INVALID_FORMAT"); - client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats"); -}); -%} - -### Test without authentication (should fail) -GET {{triliumHost}}/etapi/metrics - -> {% -client.test("Metrics endpoint requires authentication", function() { - client.assert(response.status === 401, "Response status should be 401"); -}); -%} \ No newline at end of file diff --git a/apps/server/spec/etapi/etapi-metrics.spec.ts b/apps/server/spec/etapi/etapi-metrics.spec.ts new file mode 100644 index 000000000..7b7d3a184 --- /dev/null +++ b/apps/server/spec/etapi/etapi-metrics.spec.ts @@ -0,0 +1,71 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import { login } from "./utils.js"; +import config from "../../src/services/config.js"; + +let app: Application; +let token: string; + +const USER = "etapi"; + +describe("etapi/metrics", () => { + beforeAll(async () => { + config.General.noAuthentication = false; + const buildApp = (await (import("../../src/app.js"))).default; + app = await buildApp(); + token = await login(app); + }); + + it("returns Prometheus format by default", async () => { + const response = await supertest(app) + .get("/etapi/metrics") + .auth(USER, token, { "type": "basic"}) + .expect(200); + expect(response.headers["content-type"]).toContain("text/plain"); + expect(response.text).toContain("trilium_info"); + expect(response.text).toContain("trilium_notes_total"); + expect(response.text).toContain("# HELP"); + expect(response.text).toContain("# TYPE"); + }); + + it("returns JSON when requested", async() => { + const response = await supertest(app) + .get("/etapi/metrics?format=json") + .auth(USER, token, { "type": "basic"}) + .expect(200); + expect(response.headers["content-type"]).toContain("application/json"); + expect(response.body.version).toBeTruthy(); + expect(response.body.database).toBeTruthy(); + expect(response.body.timestamp).toBeTruthy(); + expect(response.body.database.totalNotes).toBeTypeOf("number"); + expect(response.body.database.activeNotes).toBeTypeOf("number"); + expect(response.body.noteTypes).toBeTruthy(); + expect(response.body.attachmentTypes).toBeTruthy(); + expect(response.body.statistics).toBeTruthy(); + }); + + it("returns Prometheus format explicitly", async () => { + const response = await supertest(app) + .get("/etapi/metrics?format=prometheus") + .auth(USER, token, { "type": "basic"}) + .expect(200); + expect(response.headers["content-type"]).toContain("text/plain"); + expect(response.text).toContain("trilium_info"); + expect(response.text).toContain("trilium_notes_total"); + }); + + it("returns error on invalid format", async() => { + const response = await supertest(app) + .get("/etapi/metrics?format=xml") + .auth(USER, token, { "type": "basic"}) + .expect(500); + expect(response.body.message).toContain("prometheus"); + }); + + it("should fail without authentication", async() => { + await supertest(app) + .get("/etapi/metrics") + .expect(401); + }); +});