diff --git a/cypress/e2e/api/health.cy.js b/cypress/e2e/api/health.cy.js
index 318cd8d..e6d7e3a 100644
--- a/cypress/e2e/api/health.cy.js
+++ b/cypress/e2e/api/health.cy.js
@@ -1,30 +1,21 @@
///
-describe('Session Manager API', () => {
- describe('Health Check', () => {
- it('should respond to the health endpoint', () => {
- cy.request({
- method: 'GET',
- url: '/api/health',
- failOnStatusCode: false,
- }).then((response) => {
- // The endpoint should exist and return a response
- expect(response.status).to.be.oneOf([200, 503]);
- expect(response.body).to.be.an('object');
- });
- });
- });
+const api = () => Cypress.env('API_URL');
- describe('Sessions API', () => {
- it('should list sessions', () => {
- cy.request({
- method: 'GET',
- url: '/api/sessions',
- failOnStatusCode: false,
- }).then((response) => {
- expect(response.status).to.eq(200);
- expect(response.body).to.be.an('object');
- });
+describe('Health API', () => {
+ it('GET /api/health returns status and required fields', () => {
+ cy.request(`${api()}/api/health`).then((response) => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('status');
+ expect(response.body.status).to.be.oneOf([
+ 'healthy',
+ 'degraded',
+ 'unhealthy',
+ ]);
+ expect(response.body).to.have.property('docker');
+ expect(response.body).to.have.property('active_sessions');
+ expect(response.body).to.have.property('timestamp');
+ expect(response.body).to.have.property('resource_limits');
});
});
});
diff --git a/cypress/e2e/api/sessions.cy.js b/cypress/e2e/api/sessions.cy.js
new file mode 100644
index 0000000..9c6a06b
--- /dev/null
+++ b/cypress/e2e/api/sessions.cy.js
@@ -0,0 +1,97 @@
+///
+
+const api = () => Cypress.env('API_URL');
+
+describe('Sessions API', () => {
+ const createdSessions = [];
+
+ afterEach(() => {
+ createdSessions.splice(0).forEach((id) => {
+ cy.request({
+ method: 'DELETE',
+ url: `${api()}/api/sessions/${id}`,
+ failOnStatusCode: false,
+ });
+ });
+ });
+
+ describe('GET /api/sessions', () => {
+ it('returns 200 with an array', () => {
+ cy.request(`${api()}/api/sessions`).then((response) => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.be.an('array');
+ });
+ });
+ });
+
+ describe('POST /api/sessions', () => {
+ it('creates a session with expected fields', () => {
+ cy.request('POST', `${api()}/api/sessions`).then((response) => {
+ expect(response.status).to.be.oneOf([200, 201]);
+ expect(response.body).to.have.property('session_id');
+ expect(response.body).to.have.property('auth_token');
+ expect(response.body).to.have.property('status');
+ createdSessions.push(response.body.session_id);
+ });
+ });
+ });
+
+ describe('GET /api/sessions/:id', () => {
+ it('returns the created session', () => {
+ cy.request('POST', `${api()}/api/sessions`).then((createRes) => {
+ createdSessions.push(createRes.body.session_id);
+ const id = createRes.body.session_id;
+
+ cy.request(`${api()}/api/sessions/${id}`).then((response) => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('session_id', id);
+ });
+ });
+ });
+
+ it('returns 404 for nonexistent session', () => {
+ cy.request({
+ url: `${api()}/api/sessions/nonexistent-id-000`,
+ failOnStatusCode: false,
+ }).then((response) => {
+ expect(response.status).to.eq(404);
+ });
+ });
+ });
+
+ describe('DELETE /api/sessions/:id', () => {
+ it('deletes a session', () => {
+ cy.request('POST', `${api()}/api/sessions`).then((createRes) => {
+ const id = createRes.body.session_id;
+
+ cy.request('DELETE', `${api()}/api/sessions/${id}`).then(
+ (response) => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('message');
+ }
+ );
+ });
+ });
+
+ it('returns 404 for nonexistent session', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `${api()}/api/sessions/nonexistent-id-000`,
+ failOnStatusCode: false,
+ }).then((response) => {
+ expect(response.status).to.eq(404);
+ });
+ });
+ });
+
+ describe('POST /api/cleanup', () => {
+ it('returns 200 with cleanup message', () => {
+ cy.request('POST', `${api()}/api/cleanup`).then((response) => {
+ expect(response.status).to.eq(200);
+ expect(response.body)
+ .to.have.property('message')
+ .that.includes('Cleanup completed');
+ });
+ });
+ });
+});
diff --git a/cypress/e2e/smoke.cy.js b/cypress/e2e/smoke.cy.js
index d756238..6ed9f48 100644
--- a/cypress/e2e/smoke.cy.js
+++ b/cypress/e2e/smoke.cy.js
@@ -1,58 +1,7 @@
///
-/**
- * Smoke test to verify that the Cypress test infrastructure is
- * correctly installed and configured. These tests do NOT require
- * the lovdata-chat stack to be running.
- */
describe('Cypress Infrastructure Smoke Test', () => {
it('should execute a basic assertion', () => {
expect(true).to.be.true;
});
-
- it('should handle arrays and objects', () => {
- const session = {
- id: 'test-session-001',
- status: 'running',
- model: 'opencode/kimi-k2.5-free',
- };
-
- expect(session).to.have.property('id');
- expect(session.status).to.eq('running');
- expect(session.model).to.include('kimi');
- });
-
- it('should validate the allowed models whitelist structure', () => {
- const allowedModels = [
- {
- id: 'opencode/kimi-k2.5-free',
- display_name: 'Kimi K2.5',
- provider: 'zen',
- is_reasoning: false,
- is_default: true,
- },
- {
- id: 'anthropic/claude-sonnet-4-thinking',
- display_name: 'Claude Sonnet 4 (Thinking)',
- provider: 'zen',
- is_reasoning: true,
- thinking_budget_default: 10000,
- thinking_budget_max: 50000,
- },
- ];
-
- expect(allowedModels).to.have.length(2);
-
- const defaultModel = allowedModels.find((m) => m.is_default);
- expect(defaultModel).to.exist;
- expect(defaultModel.id).to.eq('opencode/kimi-k2.5-free');
-
- const reasoningModels = allowedModels.filter((m) => m.is_reasoning);
- expect(reasoningModels).to.have.length(1);
- expect(reasoningModels[0]).to.have.property('thinking_budget_default');
- expect(reasoningModels[0]).to.have.property('thinking_budget_max');
- expect(reasoningModels[0].thinking_budget_max).to.be.greaterThan(
- reasoningModels[0].thinking_budget_default
- );
- });
});
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
index 178d479..ef408a2 100644
--- a/cypress/support/e2e.js
+++ b/cypress/support/e2e.js
@@ -1,5 +1,6 @@
// Cypress E2E support file
-// Add custom commands and global hooks here
-// Prevent Cypress from failing on uncaught exceptions from the app
-Cypress.on('uncaught:exception', () => false);
+// Base URL for API tests. Override with CYPRESS_API_URL env var.
+// Not set as Cypress baseUrl to avoid server-reachability checks
+// that would block offline tests (smoke).
+Cypress.env('API_URL', Cypress.env('API_URL') || 'http://localhost');
diff --git a/package.json b/package.json
index 2ec7d56..b612e6e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,6 @@
"name": "lovdata-chat",
"version": "1.0.0",
"description": "A web-based chat interface that allows users to interact with Large Language Models (LLMs) equipped with Norwegian legal research tools from the Lovdata MCP server.",
- "main": "index.js",
"directories": {
"doc": "docs"
},
@@ -18,7 +17,6 @@
"keywords": [],
"author": "",
"license": "ISC",
- "type": "commonjs",
"devDependencies": {
"cypress": "^15.10.0"
}