test: strengthen Cypress e2e tests with real API assertions
- Remove blanket uncaught:exception suppressor (API-only tests)
- Trim smoke test to single infra-verification assertion
- Rewrite health test with strict status/field assertions, no failOnStatusCode
- Add session CRUD tests (create, get, list, delete, 404 cases, cleanup)
- Use Cypress.env('API_URL') instead of baseUrl to avoid blocking smoke tests
- Remove unused main and type fields from package.json
This commit is contained in:
@@ -1,30 +1,21 @@
|
|||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
describe('Session Manager API', () => {
|
const api = () => Cypress.env('API_URL');
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Sessions API', () => {
|
describe('Health API', () => {
|
||||||
it('should list sessions', () => {
|
it('GET /api/health returns status and required fields', () => {
|
||||||
cy.request({
|
cy.request(`${api()}/api/health`).then((response) => {
|
||||||
method: 'GET',
|
expect(response.status).to.eq(200);
|
||||||
url: '/api/sessions',
|
expect(response.body).to.have.property('status');
|
||||||
failOnStatusCode: false,
|
expect(response.body.status).to.be.oneOf([
|
||||||
}).then((response) => {
|
'healthy',
|
||||||
expect(response.status).to.eq(200);
|
'degraded',
|
||||||
expect(response.body).to.be.an('object');
|
'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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
97
cypress/e2e/api/sessions.cy.js
Normal file
97
cypress/e2e/api/sessions.cy.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,58 +1,7 @@
|
|||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
/**
|
|
||||||
* 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', () => {
|
describe('Cypress Infrastructure Smoke Test', () => {
|
||||||
it('should execute a basic assertion', () => {
|
it('should execute a basic assertion', () => {
|
||||||
expect(true).to.be.true;
|
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
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Cypress E2E support file
|
// Cypress E2E support file
|
||||||
// Add custom commands and global hooks here
|
|
||||||
|
|
||||||
// Prevent Cypress from failing on uncaught exceptions from the app
|
// Base URL for API tests. Override with CYPRESS_API_URL env var.
|
||||||
Cypress.on('uncaught:exception', () => false);
|
// 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');
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"name": "lovdata-chat",
|
"name": "lovdata-chat",
|
||||||
"version": "1.0.0",
|
"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.",
|
"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": {
|
"directories": {
|
||||||
"doc": "docs"
|
"doc": "docs"
|
||||||
},
|
},
|
||||||
@@ -18,7 +17,6 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^15.10.0"
|
"cypress": "^15.10.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user