package showcase import ( "os" "path/filepath" "reflect" "strings" "testing" "codeberg.org/snonux/gitsyncer/internal/config" ) func TestIsBackupRepo(t *testing.T) { t.Parallel() tests := []struct { name string repo string want bool }{ {name: "exact bak suffix", repo: "foo.bak", want: true}, {name: "bak dot suffix", repo: "foo.bak.20260222", want: true}, {name: "bak dot with multiple segments", repo: "foo.bak.tmp.snapshot", want: true}, {name: "backup word", repo: "foo.backup", want: false}, {name: "bak as prefix", repo: "bak.foo", want: false}, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got := isBackupRepo(tc.repo) if got != tc.want { t.Fatalf("isBackupRepo(%q) = %v, want %v", tc.repo, got, tc.want) } }) } } func TestIsExcluded_AdditiveRules(t *testing.T) { t.Parallel() g := &Generator{ config: &config.Config{ ExcludeFromShowcase: []string{"manual-exclude"}, }, } tests := []struct { name string repo string want bool }{ {name: "excluded by config", repo: "manual-exclude", want: true}, {name: "excluded by backup suffix", repo: "repo.bak", want: true}, {name: "not excluded", repo: "normal-repo", want: false}, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got := g.isExcluded(tc.repo) if got != tc.want { t.Fatalf("isExcluded(%q) = %v, want %v", tc.repo, got, tc.want) } }) } } func TestFilterExcludedRepos_RemovesBackupAndConfigRepos(t *testing.T) { t.Parallel() g := &Generator{ config: &config.Config{ ExcludeFromShowcase: []string{"manual-exclude"}, }, } repos := []string{"normal", "manual-exclude", "mirror.bak", "mirror.bak.20260222", "keep"} want := []string{"normal", "keep"} got := g.filterExcludedRepos(repos) if !reflect.DeepEqual(got, want) { t.Fatalf("filterExcludedRepos() = %#v, want %#v", got, want) } } func TestFilterExcludedRepos_EmptyConfigStillRemovesBackupRepos(t *testing.T) { t.Parallel() g := &Generator{ config: &config.Config{}, } repos := []string{"normal", "archive.bak", "archive.bak.old", "keep"} want := []string{"normal", "keep"} got := g.filterExcludedRepos(repos) if !reflect.DeepEqual(got, want) { t.Fatalf("filterExcludedRepos() = %#v, want %#v", got, want) } } func TestFormatGemtext_IncludesRankHistoryInHeader(t *testing.T) { t.Parallel() g := &Generator{config: &config.Config{}} content := g.formatGemtext([]ProjectSummary{ { Name: "alpha", Summary: "alpha summary", RankHistory: []RepoRankHistory{ {Spot: 2, Anchor: "now"}, {Spot: 3, Anchor: "1w", Arrow: "↖"}, {Spot: 3, Anchor: "2w", Arrow: "←"}, {Spot: 0, Anchor: "3w", Arrow: "·"}, {Spot: 2, Anchor: "4w", Arrow: "↙"}, }, }, }) if !strings.Contains(content, "### 1. alpha 2↖3←3↙2") { t.Fatalf("rank history was not rendered in header: %s", content) } } func TestFormatGemtext_SanitizesMarkdownHeadingsInSummary(t *testing.T) { t.Parallel() g := &Generator{config: &config.Config{}} content := g.formatGemtext([]ProjectSummary{ { Name: "alpha", Summary: "# Alpha Project\n\nconf\n====\n\nParagraph body", }, }) if strings.Contains(content, "\n# Alpha Project\n") { t.Fatalf("markdown heading leaked into gemtext summary: %s", content) } if strings.Contains(content, "\n====\n") { t.Fatalf("setext underline leaked into gemtext summary: %s", content) } if !strings.Contains(content, "\nAlpha Project\n\nconf\n\nParagraph body\n\n") { t.Fatalf("sanitized summary not rendered as expected: %s", content) } } func TestFindReadmeContent_UsesRepoPathWithoutChangingCWD(t *testing.T) { t.Parallel() repoPath := filepath.Join(t.TempDir(), "repo") if err := os.MkdirAll(repoPath, 0755); err != nil { t.Fatalf("failed to create repo dir: %v", err) } readmePath := filepath.Join(repoPath, "README.md") if err := os.WriteFile(readmePath, []byte("repo summary"), 0644); err != nil { t.Fatalf("failed to write readme: %v", err) } content, readmeFile, found := findReadmeContent(repoPath) if !found { t.Fatal("expected README to be found") } if readmeFile != "README.md" { t.Fatalf("expected README.md, got %q", readmeFile) } if string(content) != "repo summary" { t.Fatalf("unexpected README content: %q", string(content)) } } func TestFallbackSummary_UsesFirstReadmeParagraph(t *testing.T) { t.Parallel() readme := []byte("first paragraph\n\nsecond paragraph") summary := fallbackSummary("repo", readme, true) if summary != "first paragraph" { t.Fatalf("expected first paragraph summary, got %q", summary) } } func TestFallbackSummary_SkipsHeadingOnlyParagraphs(t *testing.T) { t.Parallel() readme := []byte("# repo title\n\n\n\nactual summary paragraph") summary := fallbackSummary("repo", readme, true) if summary != "actual summary paragraph" { t.Fatalf("expected summary paragraph after heading and image, got %q", summary) } } func TestExtractUsefulSummary_SkipsNonProseParagraphs(t *testing.T) { t.Parallel() input := "

\n\n

\n\n* first bullet\n* second bullet\n\nTOC:\n01. Intro\n02. Usage\n\nActual summary paragraph.\n\nSecond useful paragraph." got := extractUsefulSummary(input, 2) want := "Actual summary paragraph.\n\nSecond useful paragraph." if got != want { t.Fatalf("extractUsefulSummary() = %q, want %q", got, want) } } func TestExtractUsefulSummary_NormalizesManpageNameSection(t *testing.T) { t.Parallel() input := "NAME\n cpuinfo - A small and humble tool to print out CPU data" got := extractUsefulSummary(input, 1) want := "cpuinfo - A small and humble tool to print out CPU data" if got != want { t.Fatalf("extractUsefulSummary() = %q, want %q", got, want) } } func TestExtractUsefulSummary_SkipsFencedCodeBlocks(t *testing.T) { t.Parallel() input := "```sh\nsudo dnf install wireguard-tools\nbundler install\n```\n\nActual summary paragraph." got := extractUsefulSummary(input, 1) want := "Actual summary paragraph." if got != want { t.Fatalf("extractUsefulSummary() = %q, want %q", got, want) } }