@@ -246,13 +246,41 @@ describe('HTTP Server API V1', () => {
246246 expect ( response . status ) . toBe ( 201 )
247247 expect ( response . body ) . toHaveProperty ( 'id' )
248248 expect ( response . body ) . toHaveProperty ( 'login' , 'expressjs' )
249- expect ( response . body ) . toHaveProperty ( 'html_url' , githubOrgUrl )
249+ expect ( response . body ) . toHaveProperty ( 'html_url' , githubOrgUrl . toLowerCase ( ) )
250250 expect ( response . body ) . toHaveProperty ( 'project_id' , projectId )
251251
252+ // Verify the Location header is set correctly
253+ expect ( response . headers ) . toHaveProperty ( 'location' , `/api/v1/project/${ projectId } /gh-org/${ response . body . id } ` )
254+
252255 // Verify organization was added to the database
253256 const orgs = await getAllGithubOrganizationsByProjectsId ( [ projectId ] )
254257 expect ( orgs . length ) . toBe ( 1 )
255- expect ( orgs [ 0 ] . html_url ) . toBe ( githubOrgUrl )
258+ expect ( orgs [ 0 ] . html_url ) . toBe ( githubOrgUrl . toLowerCase ( ) )
259+ } )
260+
261+ test ( 'should correctly extract organization login from URL with trailing slash' , async ( ) => {
262+ const githubOrgUrl = 'https://github.com/expressjs/'
263+
264+ const response = await app
265+ . post ( `/api/v1/project/${ projectId } /gh-org` )
266+ . send ( { githubOrgUrl } )
267+
268+ expect ( response . status ) . toBe ( 201 )
269+ expect ( response . body ) . toHaveProperty ( 'login' , 'expressjs' )
270+ expect ( response . body ) . toHaveProperty ( 'html_url' , githubOrgUrl . toLowerCase ( ) . replace ( / \/ $ / , '' ) )
271+ } )
272+
273+ test ( 'should correctly extract organization login from URL with query parameters' , async ( ) => {
274+ const githubOrgUrl = 'https://github.com/expressjs?tab=repositories'
275+
276+ const response = await app
277+ . post ( `/api/v1/project/${ projectId } /gh-org` )
278+ . send ( { githubOrgUrl } )
279+
280+ expect ( response . status ) . toBe ( 201 )
281+ expect ( response . body ) . toHaveProperty ( 'login' , 'expressjs' )
282+ // The stored URL should be normalized without query parameters
283+ expect ( response . body . html_url ) . not . toContain ( '?' )
256284 } )
257285
258286 test ( 'should return 400 for invalid project ID' , async ( ) => {
@@ -262,7 +290,27 @@ describe('HTTP Server API V1', () => {
262290
263291 expect ( response . status ) . toBe ( 400 )
264292 expect ( response . body ) . toHaveProperty ( 'errors' )
265- expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'Invalid project ID' )
293+ expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'must be integer' )
294+ } )
295+
296+ test ( 'should return 400 for zero project ID' , async ( ) => {
297+ const response = await app
298+ . post ( '/api/v1/project/0/gh-org' )
299+ . send ( { githubOrgUrl : 'https://github.com/expressjs' } )
300+
301+ expect ( response . status ) . toBe ( 400 )
302+ expect ( response . body ) . toHaveProperty ( 'errors' )
303+ expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'Invalid project ID. Must be a positive integer.' )
304+ } )
305+
306+ test ( 'should return 400 for negative project ID' , async ( ) => {
307+ const response = await app
308+ . post ( '/api/v1/project/-1/gh-org' )
309+ . send ( { githubOrgUrl : 'https://github.com/expressjs' } )
310+
311+ expect ( response . status ) . toBe ( 400 )
312+ expect ( response . body ) . toHaveProperty ( 'errors' )
313+ expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'Invalid project ID. Must be a positive integer.' )
266314 } )
267315
268316 test ( 'should return 400 for invalid GitHub organization URL' , async ( ) => {
@@ -305,6 +353,38 @@ describe('HTTP Server API V1', () => {
305353 expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'GitHub organization already exists for this project' )
306354 } )
307355
356+ test ( 'should return 409 for duplicate GitHub organization with different case' , async ( ) => {
357+ // First add the organization with lowercase
358+ await app
359+ . post ( `/api/v1/project/${ projectId } /gh-org` )
360+ . send ( { githubOrgUrl : 'https://github.com/expressjs' } )
361+
362+ // Try to add it again with uppercase
363+ const response = await app
364+ . post ( `/api/v1/project/${ projectId } /gh-org` )
365+ . send ( { githubOrgUrl : 'https://github.com/ExpressJS' } )
366+
367+ expect ( response . status ) . toBe ( 409 )
368+ expect ( response . body ) . toHaveProperty ( 'errors' )
369+ expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'GitHub organization already exists for this project' )
370+ } )
371+
372+ test ( 'should return 409 for duplicate GitHub organization with trailing slash' , async ( ) => {
373+ // First add the organization without trailing slash
374+ await app
375+ . post ( `/api/v1/project/${ projectId } /gh-org` )
376+ . send ( { githubOrgUrl : 'https://github.com/expressjs' } )
377+
378+ // Try to add it again with trailing slash
379+ const response = await app
380+ . post ( `/api/v1/project/${ projectId } /gh-org` )
381+ . send ( { githubOrgUrl : 'https://github.com/expressjs/' } )
382+
383+ expect ( response . status ) . toBe ( 409 )
384+ expect ( response . body ) . toHaveProperty ( 'errors' )
385+ expect ( response . body . errors [ 0 ] ) . toHaveProperty ( 'message' , 'GitHub organization already exists for this project' )
386+ } )
387+
308388 test . todo ( 'should return 500 for internal server error' )
309389 } )
310390} )
0 commit comments