@@ -508,3 +508,45 @@ func TestReadDocumentContent_WithTags(t *testing.T) {
508508 t .Errorf ("tags = %v, want [frontend]" , doc .Tags )
509509 }
510510}
511+
512+ func TestValidateArchcorePath (t * testing.T ) {
513+ t .Parallel ()
514+ cases := []struct {
515+ name string
516+ input string
517+ want string
518+ wantErr bool
519+ errMatch string
520+ }{
521+ {name : "forward slashes" , input : ".archcore/features/lfrom/x.doc.md" , want : ".archcore/features/lfrom/x.doc.md" },
522+ {name : "redundant segments cleaned" , input : ".archcore/a/./b/../c.md" , want : ".archcore/a/c.md" },
523+ // Regression: filepath.Clean on Windows re-introduces backslashes, which
524+ // broke the second prefix check. The cleaned output must always use
525+ // forward slashes, regardless of platform.
526+ {name : "output uses forward slashes" , input : ".archcore/a/b.md" , want : ".archcore/a/b.md" },
527+ {name : "missing prefix" , input : "features/lfrom/x.doc.md" , wantErr : true , errMatch : "must start with" },
528+ {name : "traversal escape" , input : ".archcore/../etc/passwd" , wantErr : true , errMatch : "must be relative" },
529+ {name : "absolute unix" , input : "/etc/passwd" , wantErr : true , errMatch : "must be relative" },
530+ }
531+ for _ , tc := range cases {
532+ t .Run (tc .name , func (t * testing.T ) {
533+ t .Parallel ()
534+ got , err := validateArchcorePath (tc .input )
535+ if tc .wantErr {
536+ if err == nil {
537+ t .Fatalf ("expected error, got nil (result=%q)" , got )
538+ }
539+ if tc .errMatch != "" && ! strings .Contains (err .Error (), tc .errMatch ) {
540+ t .Errorf ("error = %q, want substring %q" , err .Error (), tc .errMatch )
541+ }
542+ return
543+ }
544+ if err != nil {
545+ t .Fatalf ("unexpected error: %v" , err )
546+ }
547+ if got != tc .want {
548+ t .Errorf ("got %q, want %q" , got , tc .want )
549+ }
550+ })
551+ }
552+ }
0 commit comments