@@ -266,3 +266,66 @@ describe("getServiceAlias", () => {
266266 expect ( Utils . getServiceAlias ( { ...base , name : "library/nginx" , alias : "my-nginx" } ) ) . toBe ( "my-nginx" ) ;
267267 } ) ;
268268} ) ;
269+
270+ describe ( "safeDockerString" , ( ) => {
271+ it ( "should return encoded name unchanged when within limit" , ( ) => {
272+ const result = Utils . safeDockerString ( "short-job-name" ) ;
273+ expect ( result ) . toBe ( "short-job-name" ) ;
274+ } ) ;
275+
276+ it ( "should encode non-alphanumeric characters" , ( ) => {
277+ const result = Utils . safeDockerString ( "job/name" ) ;
278+ expect ( result ) . toBe ( "jobLwname" ) ; // '/' → 'Lw'
279+ } ) ;
280+
281+ it ( "should truncate and hash when encoded name exceeds MAX_FILENAME_LENGTH" , ( ) => {
282+ const longName = "my-group/common/python-unit-test: [my-app-controller,My app controller to be used as reference for development teams,python311,controller,common,controller/setup.py,controller/setup_c.py,controller/setup_n.py,controller/tests/**/*,controller/coverage/*,controller/build/**/*,controller/coverage/coverage-unit.xml,75,true]" ;
283+ const result = Utils . safeDockerString ( longName ) ;
284+ expect ( result . length ) . toBeLessThanOrEqual ( Utils . MAX_FILENAME_LENGTH ) ;
285+ } ) ;
286+
287+ it ( "should produce deterministic output for the same input" , ( ) => {
288+ const longName = "a" . repeat ( 50 ) + "/" + "b" . repeat ( 200 ) ;
289+ const result1 = Utils . safeDockerString ( longName ) ;
290+ const result2 = Utils . safeDockerString ( longName ) ;
291+ expect ( result1 ) . toBe ( result2 ) ;
292+ } ) ;
293+
294+ it ( "should produce different output for different long inputs" , ( ) => {
295+ const name1 = "job: [" + "a" . repeat ( 300 ) + "]" ;
296+ const name2 = "job: [" + "b" . repeat ( 300 ) + "]" ;
297+ const result1 = Utils . safeDockerString ( name1 ) ;
298+ const result2 = Utils . safeDockerString ( name2 ) ;
299+ expect ( result1 ) . not . toBe ( result2 ) ;
300+ } ) ;
301+
302+ it ( "should handle extremely long job names (1000+ chars)" , ( ) => {
303+ const extremeName = "group/subgroup/job: [" + "x" . repeat ( 2000 ) + "]" ;
304+ const result = Utils . safeDockerString ( extremeName ) ;
305+ expect ( result . length ) . toBeLessThanOrEqual ( Utils . MAX_FILENAME_LENGTH ) ;
306+ expect ( result . length ) . toBeGreaterThan ( 16 ) ; // has prefix + hash
307+ } ) ;
308+
309+ it ( "should keep volume name within NAME_MAX=255 (worst-case suffix)" , ( ) => {
310+ const longName = "my-group/common/python-unit-test: [" + "a/b/c," . repeat ( 100 ) + "]" ;
311+ const safeJobName = Utils . safeDockerString ( longName ) ;
312+ const worstCaseVolume = `gcl-${ safeJobName } -999999-build` ;
313+ expect ( worstCaseVolume . length ) . toBeLessThanOrEqual ( 255 ) ;
314+ } ) ;
315+
316+ it ( "should not hash names that are exactly at the limit" , ( ) => {
317+ // Create a name whose encoded form is exactly MAX_FILENAME_LENGTH
318+ const name = "a" . repeat ( Utils . MAX_FILENAME_LENGTH ) ;
319+ const result = Utils . safeDockerString ( name ) ;
320+ expect ( result ) . toBe ( name ) ; // all alphanumeric, no encoding, no hash
321+ } ) ;
322+
323+ it ( "should hash names whose encoded form is one char over the limit" , ( ) => {
324+ // 'a' stays as 'a', '/' encodes to 'Lw' (2 chars)
325+ // Build a string that encodes to exactly MAX_FILENAME_LENGTH + 1
326+ const name = "a" . repeat ( Utils . MAX_FILENAME_LENGTH - 1 ) + "/" ; // '/' -> 'Lw' = +2, total = MAX+1
327+ const result = Utils . safeDockerString ( name ) ;
328+ expect ( result . length ) . toBeLessThanOrEqual ( Utils . MAX_FILENAME_LENGTH ) ;
329+ expect ( result ) . toContain ( "-" ) ; // has hash separator
330+ } ) ;
331+ } ) ;
0 commit comments