Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
pull_request:
branches:
- develop
push:
branches:
- develop

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
name: Build and test
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --legacy-peer-deps

- name: Run server tests
run: npm test

- name: Build maxun-core
run: npm ci --prefix maxun-core && npm run build --prefix maxun-core

- name: Build frontend
run: npm run build
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
"build": "vite build",
"build:server": "tsc -p server/tsconfig.json",
"preview": "vite preview",
"test": "npm run test:server",
"test:server": "npm run build:server && node --test \"tests/server/**/*.test.js\"",
"lint": "./node_modules/.bin/eslint .",
"migrate": "sequelize-cli db:migrate",
"migrate:undo": "sequelize-cli db:migrate:undo",
Expand Down
102 changes: 102 additions & 0 deletions tests/server/utils/output-validation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const assert = require('node:assert/strict');
const { describe, it } = require('node:test');

const {
getInterpretationFailureReason,
hasExpectedRobotOutput,
} = require('../../../server/dist/server/src/utils/output-validation');

describe('getInterpretationFailureReason', () => {
it('returns the first search or crawl error from interpretation logs', () => {
const reason = getInterpretationFailureReason(
[
'Starting search',
' Search execution error: DuckDuckGo returned no result container ',
'Search action failed: fallback detail',
],
'Run completed without output'
);

assert.equal(reason, 'Search execution error: DuckDuckGo returned no result container');
});

it('returns the fallback when logs are missing or do not contain a known marker', () => {
assert.equal(
getInterpretationFailureReason('not-an-array', 'Run completed without output'),
'Run completed without output'
);

assert.equal(
getInterpretationFailureReason(['Run started', 'Run ended'], 'Run completed without output'),
'Run completed without output'
);
});
});

describe('hasExpectedRobotOutput', () => {
it('accepts default search output when search results exist', () => {
assert.equal(
hasExpectedRobotOutput('search', {
search: {
'Search Results': {
results: [{ title: 'Maxun', url: 'https://www.maxun.dev' }],
},
},
}),
true
);
});

it('rejects search output when requested formats are absent', () => {
assert.equal(
hasExpectedRobotOutput(
'search',
{
search: {
'Search Results': {
results: [{ title: 'Maxun', url: 'https://www.maxun.dev' }],
},
},
},
['markdown']
),
false
);
});

it('accepts search output when a requested serializable format is present', () => {
assert.equal(
hasExpectedRobotOutput(
'search',
{
search: {
'Search Results': {
results: [{ markdown: '# Maxun', url: 'https://www.maxun.dev' }],
},
},
},
['markdown']
),
true
);
});

it('rejects crawl output when every result is an error', () => {
assert.equal(
hasExpectedRobotOutput(
'crawl',
{
crawl: {
'Crawl Results': [{ error: 'Navigation failed' }],
},
},
['text']
),
false
);
});

it('continues to accept non-search and non-crawl robots', () => {
assert.equal(hasExpectedRobotOutput('extract', {}), true);
});
});