11import { describe , it , expect , beforeAll , afterAll } from "bun:test" ;
2- import { buildChangelogResponse } from "@buildinternet/releases-core/changelog-slice" ;
2+ import {
3+ buildChangelogResponse ,
4+ selectChangelogFile ,
5+ } from "@buildinternet/releases-core/changelog-slice" ;
36import { createTestDb , type TestDatabase } from "../db-helper.js" ;
47import { eq } from "drizzle-orm" ;
58import {
@@ -9,35 +12,16 @@ import {
912 type SourceChangelogFile ,
1013} from "@buildinternet/releases-core/schema" ;
1114
12- // This test mirrors the server-side logic of `handleSourceChangelog`
13- // (src/api/routes/sources.ts) and `GET /v1/sources/:slug/changelog` in
14- // workers/api/src/routes/sources.ts without going through the getDb()
15- // singleton — both handlers share the same resolution rules:
16- // - omitted path → prefer root CHANGELOG.md, fall back to first by path
17- // - unknown path → 404 sentinel
18- // - known path → return that row
19- // - response carries `files` index + `truncated` flag
20- // Touching the singleton requires resetting mode.ts caches, which is
21- // brittle across parallel test files.
15+ // This test exercises the response-building path used by both
16+ // `handleSourceChangelog` (src/api/routes/sources.ts) and
17+ // `GET /v1/sources/:slug/changelog` (workers/api/src/routes/sources.ts).
18+ // It uses the canonical `selectChangelogFile` from the core package to pick
19+ // the row, then asserts on the `buildChangelogResponse` output — the part
20+ // that's unique to the worker layer. The not-found sentinels are covered
21+ // by the core package's own tests.
2222
2323const MB = 1024 * 1024 ;
2424
25- type ChangelogSelectResult = "not_found_source" | "not_found_path" | SourceChangelogFile ;
26-
27- function selectChangelog (
28- allRows : SourceChangelogFile [ ] ,
29- requestedPath : string | null ,
30- ) : ChangelogSelectResult {
31- if ( allRows . length === 0 ) return "not_found_source" ;
32- if ( requestedPath ) {
33- const match = allRows . find ( ( r ) => r . path === requestedPath ) ;
34- if ( ! match ) return "not_found_path" ;
35- return match ;
36- }
37- const root = allRows . find ( ( r ) => ! r . path . includes ( "/" ) ) ;
38- return root ?? allRows [ 0 ] ;
39- }
40-
4125let tdb : TestDatabase ;
4226let sourceId : string ;
4327
@@ -104,22 +88,24 @@ function buildFiles(rows: SourceChangelogFile[]) {
10488 } ) ) ;
10589}
10690
91+ function selectOrFail ( rows : SourceChangelogFile [ ] , path : string | null ) : SourceChangelogFile {
92+ const selected = selectChangelogFile ( rows , path ) ;
93+ if ( ! selected ) throw new Error ( "expected row" ) ;
94+ return selected ;
95+ }
96+
10797describe ( "source changelog route resolution" , ( ) => {
10898 it ( "returns the root file when path is omitted" , ( ) => {
10999 const rows = fetchAll ( ) ;
110- const selected = selectChangelog ( rows , null ) ;
111- expect ( selected ) . not . toBe ( "not_found_source" ) ;
112- expect ( selected ) . not . toBe ( "not_found_path" ) ;
113- if ( typeof selected === "string" ) return ;
100+ const selected = selectOrFail ( rows , null ) ;
114101 const res = buildChangelogResponse ( selected , { offset : null , limit : null } , buildFiles ( rows ) ) ;
115102 expect ( res . path ) . toBe ( "CHANGELOG.md" ) ;
116103 expect ( res . truncated ) . toBe ( false ) ;
117104 } ) ;
118105
119106 it ( "includes a files index for every tracked file" , ( ) => {
120107 const rows = fetchAll ( ) ;
121- const selected = selectChangelog ( rows , null ) ;
122- if ( typeof selected === "string" ) throw new Error ( "expected row" ) ;
108+ const selected = selectOrFail ( rows , null ) ;
123109 const res = buildChangelogResponse ( selected , { offset : null , limit : null } , buildFiles ( rows ) ) ;
124110 expect ( res . files . map ( ( f ) => f . path ) . toSorted ( ) ) . toEqual ( [
125111 "CHANGELOG.md" ,
@@ -133,36 +119,23 @@ describe("source changelog route resolution", () => {
133119
134120 it ( "resolves path=<known> to the requested file" , ( ) => {
135121 const rows = fetchAll ( ) ;
136- const selected = selectChangelog ( rows , "packages/alpha/CHANGELOG.md" ) ;
137- if ( typeof selected === "string" ) throw new Error ( "expected row" ) ;
122+ const selected = selectOrFail ( rows , "packages/alpha/CHANGELOG.md" ) ;
138123 const res = buildChangelogResponse ( selected , { offset : null , limit : null } , buildFiles ( rows ) ) ;
139124 expect ( res . path ) . toBe ( "packages/alpha/CHANGELOG.md" ) ;
140125 expect ( res . content ) . toBe ( "# alpha\n" ) ;
141126 } ) ;
142127
143- it ( "returns not_found_path for an unknown path" , ( ) => {
144- const rows = fetchAll ( ) ;
145- const selected = selectChangelog ( rows , "packages/missing/CHANGELOG.md" ) ;
146- expect ( selected ) . toBe ( "not_found_path" ) ;
147- } ) ;
148-
149128 it ( "flags truncated=true when bytes === 1MB" , ( ) => {
150129 const rows = fetchAll ( ) ;
151- const selected = selectChangelog ( rows , "packages/huge/CHANGELOG.md" ) ;
152- if ( typeof selected === "string" ) throw new Error ( "expected row" ) ;
130+ const selected = selectOrFail ( rows , "packages/huge/CHANGELOG.md" ) ;
153131 const res = buildChangelogResponse ( selected , { offset : null , limit : null } , buildFiles ( rows ) ) ;
154132 expect ( res . truncated ) . toBe ( true ) ;
155133 expect ( res . truncatedAt ) . toBe ( MB ) ;
156134 } ) ;
157135
158- it ( "returns not_found_source for empty row set" , ( ) => {
159- expect ( selectChangelog ( [ ] , null ) ) . toBe ( "not_found_source" ) ;
160- } ) ;
161-
162136 it ( "falls back to live encoding when row.tokens is null" , ( ) => {
163137 const rows = fetchAll ( ) ;
164- const selected = selectChangelog ( rows , "CHANGELOG.md" ) ;
165- if ( typeof selected === "string" ) throw new Error ( "expected row" ) ;
138+ const selected = selectOrFail ( rows , "CHANGELOG.md" ) ;
166139 const res = buildChangelogResponse (
167140 { ...selected , tokens : null } ,
168141 { offset : null , limit : null } ,
@@ -175,8 +148,7 @@ describe("source changelog route resolution", () => {
175148
176149 it ( "honors the tokens range param end-to-end through buildChangelogResponse" , ( ) => {
177150 const rows = fetchAll ( ) ;
178- const selected = selectChangelog ( rows , "CHANGELOG.md" ) ;
179- if ( typeof selected === "string" ) throw new Error ( "expected row" ) ;
151+ const selected = selectOrFail ( rows , "CHANGELOG.md" ) ;
180152 const res = buildChangelogResponse (
181153 selected ,
182154 { offset : null , limit : null , tokens : "100" } ,
0 commit comments