@@ -40,9 +40,26 @@ func TestStackpackScaffoldCommand_RequiredFlags(t *testing.T) {
4040
4141 for _ , tt := range tests {
4242 t .Run (tt .name , func (t * testing.T ) {
43+ if ! tt .wantErr {
44+ // Create temporary directory for successful command execution to avoid polluting current directory
45+ tempDir , err := os .MkdirTemp ("" , "stackpack-required-flags-test-*" )
46+ require .NoError (t , err )
47+ defer os .RemoveAll (tempDir )
48+
49+ // Change to temp directory so command uses it as default destination
50+ originalWd , err := os .Getwd ()
51+ require .NoError (t , err )
52+ defer func () {
53+ err := os .Chdir (originalWd )
54+ require .NoError (t , err , "Failed to change back to original working directory" )
55+ }()
56+
57+ err = os .Chdir (tempDir )
58+ require .NoError (t , err )
59+ }
60+
4361 cli , cmd := setupStackpackScaffoldCmd (t )
4462 _ , err := di .ExecuteCommandWithContext (& cli .Deps , cmd , tt .args ... )
45- defer removeStackPack ()
4663
4764 if tt .wantErr {
4865 require .Error (t , err )
@@ -64,7 +81,6 @@ func TestStackpackScaffoldCommand_MutuallyExclusiveFlags(t *testing.T) {
6481 "--template-local-dir" , "./templates" ,
6582 "--template-github-repo" , "owner/repo" ,
6683 )
67- defer removeStackPack ()
6884 require .Error (t , err )
6985 assert .Contains (t , err .Error (), "template-github-repo" )
7086}
@@ -240,8 +256,6 @@ func TestStackpackScaffoldCommand_LocalDirSource(t *testing.T) {
240256 cli , cmd := setupStackpackScaffoldCmd (t )
241257
242258 _ , err := di .ExecuteCommandWithContext (& cli .Deps , cmd , tt .args ... )
243- defer removeStackPack ()
244-
245259 if tt .wantErr {
246260 require .Error (t , err )
247261 } else {
@@ -318,7 +332,6 @@ func TestStackpackScaffoldCommand_JSONOutput(t *testing.T) {
318332 cli , cmd := setupStackpackScaffoldCmd (t )
319333
320334 _ , err := di .ExecuteCommandWithContext (& cli .Deps , cmd , tt .args ... )
321- defer removeStackPack ()
322335 require .NoError (t , err )
323336
324337 if tt .expectJson {
@@ -381,7 +394,6 @@ func TestStackpackScaffoldCommand_ForceFlag(t *testing.T) {
381394 "--template-name" , "generic" ,
382395 "--destination-dir" , destDir ,
383396 )
384- defer removeStackPack ()
385397 require .Error (t , err )
386398 assert .Contains (t , err .Error (), "conflicting files exist" )
387399 })
@@ -397,7 +409,6 @@ func TestStackpackScaffoldCommand_ForceFlag(t *testing.T) {
397409 "--force" ,
398410 )
399411
400- defer removeStackPack ()
401412 require .NoError (t , err )
402413
403414 // Verify the file was overwritten
@@ -441,7 +452,6 @@ template: <<.TemplateName>>
441452 "--template-name" , "custom-template" ,
442453 "--destination-dir" , destDir ,
443454 )
444- defer removeStackPack ()
445455
446456 require .NoError (t , err )
447457
@@ -609,7 +619,6 @@ display_name: <<.DisplayName>>
609619 cli , cmd := setupStackpackScaffoldCmd (t )
610620
611621 _ , err := di .ExecuteCommandWithContext (& cli .Deps , cmd , tt .args ... )
612- defer removeStackPack ()
613622 require .NoError (t , err , tt .description )
614623
615624 // Verify the template variables were substituted correctly
@@ -627,7 +636,238 @@ display_name: <<.DisplayName>>
627636 }
628637}
629638
630- func removeStackPack () {
631- _ = os .RemoveAll ("src" )
632- _ = os .Remove ("version.sbt" )
639+ //nolint:funlen
640+ func TestValidateStackpackName (t * testing.T ) {
641+ tests := []struct {
642+ name string
643+ stackName string
644+ wantErr bool
645+ errorSubstr string
646+ }{
647+ // Valid names
648+ {
649+ name : "valid single letter" ,
650+ stackName : "a" ,
651+ wantErr : false ,
652+ },
653+ {
654+ name : "valid simple name" ,
655+ stackName : "app" ,
656+ wantErr : false ,
657+ },
658+ {
659+ name : "valid name with hyphens" ,
660+ stackName : "my-stackpack" ,
661+ wantErr : false ,
662+ },
663+ {
664+ name : "valid name with numbers" ,
665+ stackName : "app123" ,
666+ wantErr : false ,
667+ },
668+ {
669+ name : "valid complex name" ,
670+ stackName : "my-app-v2-123" ,
671+ wantErr : false ,
672+ },
673+ // Invalid names - starts with digit
674+ {
675+ name : "invalid starts with digit" ,
676+ stackName : "1-stackpack" ,
677+ wantErr : true ,
678+ errorSubstr : "must start with a lowercase letter [a-z]" ,
679+ },
680+ {
681+ name : "invalid starts with number" ,
682+ stackName : "2app" ,
683+ wantErr : true ,
684+ errorSubstr : "must start with a lowercase letter [a-z]" ,
685+ },
686+ // Invalid names - starts with hyphen
687+ {
688+ name : "invalid starts with hyphen" ,
689+ stackName : "-stackpack" ,
690+ wantErr : true ,
691+ errorSubstr : "must start with a lowercase letter [a-z]" ,
692+ },
693+ // Invalid names - uppercase letters
694+ {
695+ name : "invalid uppercase first letter" ,
696+ stackName : "My-Stackpack" ,
697+ wantErr : true ,
698+ errorSubstr : "must start with a lowercase letter [a-z]" ,
699+ },
700+ {
701+ name : "invalid camelCase" ,
702+ stackName : "myApp" ,
703+ wantErr : true ,
704+ errorSubstr : "lowercase letters, digits, and hyphens" ,
705+ },
706+ {
707+ name : "invalid all uppercase" ,
708+ stackName : "APP" ,
709+ wantErr : true ,
710+ errorSubstr : "must start with a lowercase letter [a-z]" ,
711+ },
712+ // Invalid names - spaces
713+ {
714+ name : "invalid with spaces" ,
715+ stackName : "my stackpack" ,
716+ wantErr : true ,
717+ errorSubstr : "lowercase letters, digits, and hyphens" ,
718+ },
719+ {
720+ name : "invalid app name with space" ,
721+ stackName : "app name" ,
722+ wantErr : true ,
723+ errorSubstr : "lowercase letters, digits, and hyphens" ,
724+ },
725+ // Invalid names - special characters
726+ {
727+ name : "invalid underscore" ,
728+ stackName : "my_stackpack" ,
729+ wantErr : true ,
730+ errorSubstr : "lowercase letters, digits, and hyphens" ,
731+ },
732+ {
733+ name : "invalid at symbol" ,
734+ stackName : "app@domain" ,
735+ wantErr : true ,
736+ errorSubstr : "lowercase letters, digits, and hyphens" ,
737+ },
738+ {
739+ name : "invalid dot" ,
740+ stackName : "stack.pack" ,
741+ wantErr : true ,
742+ errorSubstr : "lowercase letters, digits, and hyphens" ,
743+ },
744+ {
745+ name : "invalid slash" ,
746+ stackName : "my/app" ,
747+ wantErr : true ,
748+ errorSubstr : "lowercase letters, digits, and hyphens" ,
749+ },
750+ {
751+ name : "invalid colon" ,
752+ stackName : "app:v1" ,
753+ wantErr : true ,
754+ errorSubstr : "lowercase letters, digits, and hyphens" ,
755+ },
756+ }
757+
758+ for _ , tt := range tests {
759+ t .Run (tt .name , func (t * testing.T ) {
760+ err := validateStackpackName (tt .stackName )
761+ if tt .wantErr {
762+ require .Error (t , err , "Expected validation to fail for name '%s'" , tt .stackName )
763+ if tt .errorSubstr != "" {
764+ assert .Contains (t , err .Error (), tt .errorSubstr , "Error message should contain expected substring" )
765+ }
766+ assert .Contains (t , err .Error (), tt .stackName , "Error message should contain the invalid name" )
767+ } else {
768+ require .NoError (t , err , "Expected validation to pass for name '%s'" , tt .stackName )
769+ }
770+ })
771+ }
772+ }
773+
774+ func TestStackpackScaffoldCommand_NameValidation (t * testing.T ) {
775+ // Create temporary template directory for all tests to avoid GitHub template conflicts
776+ tempDir , err := os .MkdirTemp ("" , "stackpack-validation-test-*" )
777+ require .NoError (t , err )
778+ defer os .RemoveAll (tempDir )
779+
780+ // Create a mock template structure
781+ templateDir := filepath .Join (tempDir , "templates" )
782+ genericTemplateDir := filepath .Join (templateDir , "generic" )
783+ err = os .MkdirAll (genericTemplateDir , 0755 )
784+ require .NoError (t , err )
785+
786+ // Create a simple test template file
787+ templateFile := filepath .Join (genericTemplateDir , "test.txt" )
788+ err = os .WriteFile (templateFile , []byte ("Test stackpack: <<.Name>>" ), 0644 )
789+ require .NoError (t , err )
790+
791+ // Create destination directory
792+ destDir := filepath .Join (tempDir , "destination" )
793+ err = os .MkdirAll (destDir , 0755 )
794+ require .NoError (t , err )
795+
796+ tests := []struct {
797+ name string
798+ args []string
799+ wantErr bool
800+ errorSubstr string
801+ }{
802+ {
803+ name : "valid name passes validation" ,
804+ args : []string {
805+ "--name" , "my-stackpack" ,
806+ "--template-local-dir" , templateDir ,
807+ "--destination-dir" , destDir ,
808+ },
809+ wantErr : false ,
810+ },
811+ {
812+ name : "invalid name with uppercase fails" ,
813+ args : []string {
814+ "--name" , "My-Stackpack" ,
815+ "--template-local-dir" , templateDir ,
816+ "--destination-dir" , destDir ,
817+ },
818+ wantErr : true ,
819+ errorSubstr : "must start with a lowercase letter [a-z]" ,
820+ },
821+ {
822+ name : "invalid name with spaces fails" ,
823+ args : []string {
824+ "--name" , "my stackpack" ,
825+ "--template-local-dir" , templateDir ,
826+ "--destination-dir" , destDir ,
827+ },
828+ wantErr : true ,
829+ errorSubstr : "lowercase letters, digits, and hyphens" ,
830+ },
831+ {
832+ name : "invalid name starting with number fails" ,
833+ args : []string {
834+ "--name" , "1-app" ,
835+ "--template-local-dir" , templateDir ,
836+ "--destination-dir" , destDir ,
837+ },
838+ wantErr : true ,
839+ errorSubstr : "must start with a lowercase letter [a-z]" ,
840+ },
841+ {
842+ name : "invalid name with underscore fails" ,
843+ args : []string {
844+ "--name" , "my_app" ,
845+ "--template-local-dir" , templateDir ,
846+ "--destination-dir" , destDir ,
847+ },
848+ wantErr : true ,
849+ errorSubstr : "lowercase letters, digits, and hyphens" ,
850+ },
851+ }
852+
853+ for _ , tt := range tests {
854+ t .Run (tt .name , func (t * testing.T ) {
855+ cli , cmd := setupStackpackScaffoldCmd (t )
856+
857+ _ , err := di .ExecuteCommandWithContext (& cli .Deps , cmd , tt .args ... )
858+
859+ if tt .wantErr {
860+ require .Error (t , err , "Expected command to fail for invalid name" )
861+ if tt .errorSubstr != "" {
862+ assert .Contains (t , err .Error (), tt .errorSubstr , "Error message should contain expected validation message" )
863+ }
864+ } else {
865+ require .NoError (t , err , "Expected command to succeed for valid name" )
866+
867+ // Clean up destination for next test iteration
868+ _ = os .RemoveAll (destDir )
869+ _ = os .MkdirAll (destDir , 0755 )
870+ }
871+ })
872+ }
633873}
0 commit comments