Skip to content

Commit 9c75327

Browse files
test: add integration and frontend test suite with search limit validation
1 parent 650a4a9 commit 9c75327

6 files changed

Lines changed: 65 additions & 15 deletions

File tree

.coverage

0 Bytes
Binary file not shown.

.github/workflows/ci.yml

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ jobs:
1414
prod-install-smoke:
1515
name: Verify prod requirements.txt is sufficient
1616
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
1719
steps:
18-
- uses: actions/checkout@v4
20+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
21+
with:
22+
persist-credentials: false
1923

20-
- uses: actions/setup-python@v5
24+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
2125
with:
2226
python-version: "3.12"
2327
cache: pip
@@ -41,10 +45,14 @@ jobs:
4145
4246
pytest:
4347
runs-on: ubuntu-latest
48+
permissions:
49+
contents: read
4450
steps:
45-
- uses: actions/checkout@v4
51+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
52+
with:
53+
persist-credentials: false
4654

47-
- uses: actions/setup-python@v5
55+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
4856
with:
4957
python-version: "3.12"
5058
cache: pip
@@ -61,10 +69,15 @@ jobs:
6169
integration-tests:
6270
name: API integration tests + coverage
6371
runs-on: ubuntu-latest
72+
permissions:
73+
contents: read
74+
actions: write
6475
steps:
65-
- uses: actions/checkout@v4
76+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
77+
with:
78+
persist-credentials: false
6679

67-
- uses: actions/setup-python@v5
80+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
6881
with:
6982
python-version: "3.12"
7083
cache: pip
@@ -75,21 +88,26 @@ jobs:
7588
- name: Install dev dependencies
7689
run: pip install -r requirements-dev.txt
7790

91+
# Subset run: skip fail-under (full suite enforces 60% in pytest job).
7892
- name: Run integration tests with coverage
79-
run: pytest tests/test_api_integration.py tests/test_search.py -v --cov=api --cov-report=xml
93+
run: pytest tests/test_api_integration.py tests/test_search.py -v --cov=api --cov-report=xml --cov-fail-under=0
8094

81-
- uses: actions/upload-artifact@v4
95+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
8296
with:
8397
name: coverage-report
8498
path: coverage.xml
8599

86100
js-tests:
87101
name: Frontend unit tests (vitest)
88102
runs-on: ubuntu-latest
103+
permissions:
104+
contents: read
89105
steps:
90-
- uses: actions/checkout@v4
106+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
107+
with:
108+
persist-credentials: false
91109

92-
- uses: actions/setup-node@v4
110+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
93111
with:
94112
node-version: "20"
95113
cache: npm

api/search.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717
def _parse_limit(raw: str | None) -> int:
1818
"""Parse positive integer limit query param; raise ValueError if invalid."""
19-
if raw is None or raw == "":
19+
if raw is None or raw.strip() == "":
2020
return _DEFAULT_LIMIT
2121
try:
22-
n = int(raw)
22+
n = int(raw.strip())
2323
except ValueError:
2424
raise ValueError("limit must be a positive integer") from None
2525
if n < 1:

static/js/shared/markdown.test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { describe, it, expect, vi, beforeEach } from 'vitest';
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22
import { cleanContent, renderMarkdown } from './markdown.js';
33

4+
const _origMarked = globalThis.marked;
5+
const _origDOMPurify = globalThis.DOMPurify;
6+
47
describe('cleanContent', () => {
58
it('strips system-reminder blocks', () => {
69
const raw = 'Hello<system-reminder>secret</system-reminder> world';
@@ -22,6 +25,12 @@ describe('renderMarkdown', () => {
2225
};
2326
});
2427

28+
afterEach(() => {
29+
vi.restoreAllMocks();
30+
globalThis.marked = _origMarked;
31+
globalThis.DOMPurify = _origDOMPurify;
32+
});
33+
2534
it('sanitizes script tags from parsed output', () => {
2635
globalThis.marked.parse.mockReturnValue('<p>ok</p><script>alert(1)</script>');
2736
const html = renderMarkdown('# Hello');

static/js/shared/state.test.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, afterEach } from 'vitest';
22
import { state } from './state.js';
33

44
describe('state', () => {
5+
afterEach(() => {
6+
state.currentProject = null;
7+
state.cachedSessions = [];
8+
state.projectDisplayNames = {};
9+
state.navInProgress = false;
10+
});
11+
512
it('initializes with null current project', () => {
613
expect(state.currentProject).toBeNull();
714
});
@@ -13,6 +20,5 @@ describe('state', () => {
1320
it('allows updating currentProject', () => {
1421
state.currentProject = 'test-project';
1522
expect(state.currentProject).toBe('test-project');
16-
state.currentProject = null;
1723
});
1824
});

tests/test_search.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ def test_limit_default(client):
4545
assert resp.status_code == 200
4646

4747

48+
def test_limit_whitespace_defaults(client):
49+
resp = client.get("/api/search?q=Hello&limit=%20%20%20")
50+
assert resp.status_code == 200
51+
52+
53+
def test_limit_zero(client):
54+
resp = client.get("/api/search?q=Hello&limit=0")
55+
assert resp.status_code == 400
56+
assert "error" in resp.get_json()
57+
58+
59+
def test_limit_negative(client):
60+
resp = client.get("/api/search?q=Hello&limit=-1")
61+
assert resp.status_code == 400
62+
assert "error" in resp.get_json()
63+
64+
4865
def test_empty_query(client):
4966
resp = client.get("/api/search?q=")
5067
assert resp.status_code == 200

0 commit comments

Comments
 (0)