22 * Module dependencies.
33 */
44import { jest } from '@jest/globals' ;
5- import axios from 'axios' ;
65import jwt from 'jsonwebtoken' ;
76import mongoose from 'mongoose' ;
87import path from 'path' ;
@@ -26,16 +25,6 @@ describe('Home integration tests:', () => {
2625
2726 // init
2827 beforeAll ( async ( ) => {
29- // Mock GitHub API calls to avoid real network requests in tests
30- jest . spyOn ( axios , 'get' ) . mockImplementation ( async ( url ) => {
31- if ( url . includes ( '/releases' ) ) {
32- return { data : [ { name : 'v1.0.0' , prerelease : false , published_at : '2024-01-01T00:00:00Z' } ] } ;
33- }
34- if ( url . includes ( '/contents/' ) ) {
35- return { data : { content : Buffer . from ( '# Changelog\n## v1.0.0\n- First release' ) . toString ( 'base64' ) } } ;
36- }
37- throw new Error ( `Unexpected GitHub API URL: ${ url } ` ) ;
38- } ) ;
3928 try {
4029 originalOrganizationsEnabled = config . organizations . enabled ;
4130 config . organizations . enabled = false ;
@@ -79,26 +68,20 @@ describe('Home integration tests:', () => {
7968 // into later describes (#3472). Auth-required cases still use explicit
8069 // `set('Cookie', 'TOKEN=...')` headers derived from JWTs.
8170 describe ( 'Logout' , ( ) => {
82- test ( 'should be able to get releases' , async ( ) => {
83- const result = await request ( app ) . get ( '/api/home/releases' ) . expect ( 200 ) ;
84- expect ( result . body . type ) . toBe ( 'success' ) ;
85- expect ( result . body . message ) . toBe ( 'releases' ) ;
86- expect ( result . body . data ) . toBeInstanceOf ( Array ) ;
87- } ) ;
88-
89- test ( 'should be able to get changelogs' , async ( ) => {
90- const result = await request ( app ) . get ( '/api/home/changelogs' ) . expect ( 200 ) ;
91- expect ( result . body . type ) . toBe ( 'success' ) ;
92- expect ( result . body . message ) . toBe ( 'changelogs' ) ;
93- expect ( result . body . data ) . toBeInstanceOf ( Array ) ;
94- } ) ;
95-
96- test ( 'should be able to get team members' , async ( ) => {
71+ test ( 'should be able to get team members with public fields only' , async ( ) => {
9772 try {
9873 const result = await request ( app ) . get ( '/api/home/team' ) . expect ( 200 ) ;
9974 expect ( result . body . type ) . toBe ( 'success' ) ;
10075 expect ( result . body . message ) . toBe ( 'team list' ) ;
10176 expect ( result . body . data ) . toBeInstanceOf ( Array ) ;
77+ // Assert the strict public-field allowlist: every key must be one of these,
78+ // so no unexpected field (incl. _id / id / sensitive data) can ever leak.
79+ const ALLOWED_FIELDS = new Set ( [ 'firstName' , 'lastName' , 'bio' , 'position' , 'avatar' ] ) ;
80+ result . body . data . forEach ( ( member ) => {
81+ Object . keys ( member ) . forEach ( ( key ) => {
82+ expect ( ALLOWED_FIELDS . has ( key ) ) . toBe ( true ) ;
83+ } ) ;
84+ } ) ;
10285 } catch ( err ) {
10386 expect ( err ) . toBeFalsy ( ) ;
10487 console . log ( err ) ;
@@ -131,51 +114,6 @@ describe('Home integration tests:', () => {
131114 }
132115 } ) ;
133116
134- test ( 'should return empty releases gracefully when GitHub API fails' , async ( ) => {
135- axios . get . mockRejectedValueOnce ( new Error ( 'GitHub API unavailable' ) ) ;
136- const result = await request ( app ) . get ( '/api/home/releases' ) . expect ( 200 ) ;
137- expect ( result . body . type ) . toBe ( 'success' ) ;
138- expect ( result . body . message ) . toBe ( 'releases' ) ;
139- expect ( result . body . data ) . toEqual ( [ ] ) ;
140- } ) ;
141-
142- test ( 'should return empty changelogs gracefully when GitHub API fails' , async ( ) => {
143- axios . get . mockRejectedValueOnce ( new Error ( 'GitHub API unavailable' ) ) ;
144- const result = await request ( app ) . get ( '/api/home/changelogs' ) . expect ( 200 ) ;
145- expect ( result . body . type ) . toBe ( 'success' ) ;
146- expect ( result . body . message ) . toBe ( 'changelogs' ) ;
147- expect ( result . body . data ) . toEqual ( [ ] ) ;
148- } ) ;
149-
150- test ( 'should use Authorization header when a token is configured for releases' , async ( ) => {
151- const originalRepos = config . repos ;
152- axios . get . mockClear ( ) ;
153- // Temporarily set a fake token to cover the token-truthy branch in home.service releases()
154- config . repos = originalRepos . map ( ( repo ) => ( { ...repo , token : 'fake-test-token' } ) ) ;
155- const result = await request ( app ) . get ( '/api/home/releases' ) . expect ( 200 ) ;
156- expect ( result . body . type ) . toBe ( 'success' ) ;
157- const releaseCalls = axios . get . mock . calls . filter ( ( [ url ] ) => url . includes ( '/releases' ) ) ;
158- expect ( releaseCalls . length ) . toBeGreaterThan ( 0 ) ;
159- releaseCalls . forEach ( ( [ , options ] ) => {
160- expect ( options . headers . Authorization ) . toBe ( 'token fake-test-token' ) ;
161- } ) ;
162- config . repos = originalRepos ;
163- } ) ;
164-
165- test ( 'should use Authorization header when a token is configured for changelogs' , async ( ) => {
166- const originalRepos = config . repos ;
167- axios . get . mockClear ( ) ;
168- config . repos = originalRepos . map ( ( repo ) => ( { ...repo , token : 'fake-test-token' } ) ) ;
169- const result = await request ( app ) . get ( '/api/home/changelogs' ) . expect ( 200 ) ;
170- expect ( result . body . type ) . toBe ( 'success' ) ;
171- const changelogCalls = axios . get . mock . calls . filter ( ( [ url ] ) => url . includes ( '/contents/' ) ) ;
172- expect ( changelogCalls . length ) . toBeGreaterThan ( 0 ) ;
173- changelogCalls . forEach ( ( [ , options ] ) => {
174- expect ( options . headers . Authorization ) . toBe ( 'token fake-test-token' ) ;
175- } ) ;
176- config . repos = originalRepos ;
177- } ) ;
178-
179117 test ( 'should return minimal health status without auth' , async ( ) => {
180118 const result = await request ( app ) . get ( '/api/health' ) . expect ( 200 ) ;
181119 expect ( result . body . type ) . toBe ( 'success' ) ;
0 commit comments