@@ -224,6 +224,135 @@ func TestPublishEndpoint(t *testing.T) {
224224 setupRegistryService : func (_ service.RegistryService ) {},
225225 expectedStatus : http .StatusOK ,
226226 },
227+ // IB-2-registry: Integration test for multi-slash server name rejection
228+ {
229+ name : "invalid server name - multiple slashes (two slashes)" ,
230+ requestBody : apiv0.ServerJSON {
231+ Name : "com.example/server/path" ,
232+ Description : "Server with multiple slashes in name" ,
233+ Version : "1.0.0" ,
234+ Repository : model.Repository {
235+ URL : "https://github.com/example/test-server" ,
236+ Source : "github" ,
237+ ID : "example/test-server" ,
238+ },
239+ },
240+ tokenClaims : & auth.JWTClaims {
241+ AuthMethod : auth .MethodNone ,
242+ Permissions : []auth.Permission {
243+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
244+ },
245+ },
246+ setupRegistryService : func (_ service.RegistryService ) {},
247+ expectedStatus : http .StatusBadRequest ,
248+ expectedError : "server name cannot contain multiple slashes" ,
249+ },
250+ {
251+ name : "invalid server name - multiple slashes (three slashes)" ,
252+ requestBody : apiv0.ServerJSON {
253+ Name : "org.company/dept/team/project" ,
254+ Description : "Server with three slashes in name" ,
255+ Version : "1.0.0" ,
256+ },
257+ tokenClaims : & auth.JWTClaims {
258+ AuthMethod : auth .MethodNone ,
259+ Permissions : []auth.Permission {
260+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
261+ },
262+ },
263+ setupRegistryService : func (_ service.RegistryService ) {},
264+ expectedStatus : http .StatusBadRequest ,
265+ expectedError : "server name cannot contain multiple slashes" ,
266+ },
267+ {
268+ name : "invalid server name - consecutive slashes" ,
269+ requestBody : apiv0.ServerJSON {
270+ Name : "com.example//double-slash" ,
271+ Description : "Server with consecutive slashes" ,
272+ Version : "1.0.0" ,
273+ },
274+ tokenClaims : & auth.JWTClaims {
275+ AuthMethod : auth .MethodNone ,
276+ Permissions : []auth.Permission {
277+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
278+ },
279+ },
280+ setupRegistryService : func (_ service.RegistryService ) {},
281+ expectedStatus : http .StatusBadRequest ,
282+ expectedError : "server name cannot contain multiple slashes" ,
283+ },
284+ {
285+ name : "invalid server name - URL-like path" ,
286+ requestBody : apiv0.ServerJSON {
287+ Name : "com.example/servers/v1/api" ,
288+ Description : "Server with URL-like path structure" ,
289+ Version : "1.0.0" ,
290+ },
291+ tokenClaims : & auth.JWTClaims {
292+ AuthMethod : auth .MethodNone ,
293+ Permissions : []auth.Permission {
294+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
295+ },
296+ },
297+ setupRegistryService : func (_ service.RegistryService ) {},
298+ expectedStatus : http .StatusBadRequest ,
299+ expectedError : "server name cannot contain multiple slashes" ,
300+ },
301+ {
302+ name : "invalid server name - many slashes" ,
303+ requestBody : apiv0.ServerJSON {
304+ Name : "a/b/c/d/e/f" ,
305+ Description : "Server with many slashes" ,
306+ Version : "1.0.0" ,
307+ },
308+ tokenClaims : & auth.JWTClaims {
309+ AuthMethod : auth .MethodNone ,
310+ Permissions : []auth.Permission {
311+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
312+ },
313+ },
314+ setupRegistryService : func (_ service.RegistryService ) {},
315+ expectedStatus : http .StatusBadRequest ,
316+ expectedError : "server name cannot contain multiple slashes" ,
317+ },
318+ {
319+ name : "invalid server name - with packages and remotes" ,
320+ requestBody : apiv0.ServerJSON {
321+ Name : "com.example/test/server/v2" ,
322+ Description : "Complex server with invalid name" ,
323+ Version : "2.0.0" ,
324+ Repository : model.Repository {
325+ URL : "https://github.com/example/test-server" ,
326+ Source : "github" ,
327+ ID : "example/test-server" ,
328+ },
329+ Packages : []model.Package {
330+ {
331+ RegistryType : model .RegistryTypeNPM ,
332+ Identifier : "test-package" ,
333+ Version : "2.0.0" ,
334+ Transport : model.Transport {
335+ Type : model .TransportTypeStdio ,
336+ },
337+ },
338+ },
339+ Remotes : []model.Transport {
340+ {
341+ Type : model .TransportTypeStreamableHTTP ,
342+ URL : "https://example.com/api" ,
343+ },
344+ },
345+ },
346+ tokenClaims : & auth.JWTClaims {
347+ AuthMethod : auth .MethodNone ,
348+ Permissions : []auth.Permission {
349+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
350+ },
351+ },
352+ setupRegistryService : func (_ service.RegistryService ) {},
353+ expectedStatus : http .StatusBadRequest ,
354+ expectedError : "server name cannot contain multiple slashes" ,
355+ },
227356 }
228357
229358 for _ , tc := range testCases {
@@ -279,3 +408,105 @@ func TestPublishEndpoint(t *testing.T) {
279408 })
280409 }
281410}
411+
412+ // TestPublishEndpoint_MultipleSlashesEdgeCases tests additional edge cases for multi-slash validation
413+ func TestPublishEndpoint_MultipleSlashesEdgeCases (t * testing.T ) {
414+ testSeed := make ([]byte , ed25519 .SeedSize )
415+ _ , err := rand .Read (testSeed )
416+ require .NoError (t , err )
417+ testConfig := & config.Config {
418+ JWTPrivateKey : hex .EncodeToString (testSeed ),
419+ EnableRegistryValidation : false ,
420+ }
421+
422+ testCases := []struct {
423+ name string
424+ serverName string
425+ expectedStatus int
426+ description string
427+ }{
428+ {
429+ name : "valid - single slash" ,
430+ serverName : "com.example/server" ,
431+ expectedStatus : http .StatusOK ,
432+ description : "Valid server name with single slash should succeed" ,
433+ },
434+ {
435+ name : "invalid - trailing slash after valid name" ,
436+ serverName : "com.example/server/" ,
437+ expectedStatus : http .StatusBadRequest ,
438+ description : "Trailing slash creates multiple slashes" ,
439+ },
440+ {
441+ name : "invalid - leading and middle slash" ,
442+ serverName : "/com.example/server" ,
443+ expectedStatus : http .StatusBadRequest ,
444+ description : "Leading slash with middle slash" ,
445+ },
446+ {
447+ name : "invalid - file system style path" ,
448+ serverName : "usr/local/bin/server" ,
449+ expectedStatus : http .StatusBadRequest ,
450+ description : "File system style paths should be rejected" ,
451+ },
452+ {
453+ name : "invalid - version-like suffix" ,
454+ serverName : "com.example/server/v1.0.0" ,
455+ expectedStatus : http .StatusBadRequest ,
456+ description : "Version suffixes with slash should be rejected" ,
457+ },
458+ }
459+
460+ for _ , tc := range testCases {
461+ t .Run (tc .name , func (t * testing.T ) {
462+ // Create registry service
463+ registryService := service .NewRegistryService (database .NewMemoryDB (), testConfig )
464+
465+ // Create a new ServeMux and Huma API
466+ mux := http .NewServeMux ()
467+ api := humago .New (mux , huma .DefaultConfig ("Test API" , "1.0.0" ))
468+
469+ // Register the endpoint
470+ v0 .RegisterPublishEndpoint (api , registryService , testConfig )
471+
472+ // Create request body
473+ requestBody := apiv0.ServerJSON {
474+ Name : tc .serverName ,
475+ Description : "Test server" ,
476+ Version : "1.0.0" ,
477+ }
478+
479+ bodyBytes , err := json .Marshal (requestBody )
480+ require .NoError (t , err )
481+
482+ // Create request
483+ req , err := http .NewRequestWithContext (context .Background (), http .MethodPost , "/v0/publish" , bytes .NewBuffer (bodyBytes ))
484+ require .NoError (t , err )
485+ req .Header .Set ("Content-Type" , "application/json" )
486+
487+ // Set auth header with permissions
488+ tokenClaims := auth.JWTClaims {
489+ AuthMethod : auth .MethodNone ,
490+ Permissions : []auth.Permission {
491+ {Action : auth .PermissionActionPublish , ResourcePattern : "*" },
492+ },
493+ }
494+ token , err := generateTestJWTToken (testConfig , tokenClaims )
495+ require .NoError (t , err )
496+ req .Header .Set ("Authorization" , "Bearer " + token )
497+
498+ // Perform request
499+ rr := httptest .NewRecorder ()
500+ mux .ServeHTTP (rr , req )
501+
502+ // Assertions
503+ assert .Equal (t , tc .expectedStatus , rr .Code ,
504+ "%s: expected status %d, got %d" , tc .description , tc .expectedStatus , rr .Code )
505+
506+ if tc .expectedStatus == http .StatusBadRequest {
507+ assert .Contains (t , rr .Body .String (), "server name cannot contain multiple slashes" ,
508+ "%s: should contain specific error message" , tc .description )
509+ }
510+ })
511+ }
512+ }
0 commit comments