Files

907 lines
19 KiB
Go

package appview
import (
"bytes"
"strings"
"testing"
"time"
)
func TestTimeAgo(t *testing.T) {
now := time.Now()
tests := []struct {
name string
time time.Time
expected string
}{
{
name: "just now - 30 seconds ago",
time: now.Add(-30 * time.Second),
expected: "just now",
},
{
name: "1 minute ago",
time: now.Add(-1 * time.Minute),
expected: "1 minute ago",
},
{
name: "5 minutes ago",
time: now.Add(-5 * time.Minute),
expected: "5 minutes ago",
},
{
name: "45 minutes ago",
time: now.Add(-45 * time.Minute),
expected: "45 minutes ago",
},
{
name: "1 hour ago",
time: now.Add(-1 * time.Hour),
expected: "1 hour ago",
},
{
name: "3 hours ago",
time: now.Add(-3 * time.Hour),
expected: "3 hours ago",
},
{
name: "23 hours ago",
time: now.Add(-23 * time.Hour),
expected: "23 hours ago",
},
{
name: "1 day ago",
time: now.Add(-24 * time.Hour),
expected: "1 day ago",
},
{
name: "5 days ago",
time: now.Add(-5 * 24 * time.Hour),
expected: "5 days ago",
},
{
name: "30 days ago",
time: now.Add(-30 * 24 * time.Hour),
expected: "30 days ago",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
// Execute template using timeAgo function
templateStr := `{{ timeAgo . }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
err = temp.Execute(buf, tt.time)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("timeAgo() = %q, want %q", got, tt.expected)
}
})
}
}
func TestHumanizeBytes(t *testing.T) {
tests := []struct {
name string
bytes int64
expected string
}{
{
name: "0 bytes",
bytes: 0,
expected: "0 B",
},
{
name: "512 bytes",
bytes: 512,
expected: "512 B",
},
{
name: "1023 bytes",
bytes: 1023,
expected: "1023 B",
},
{
name: "1 KB",
bytes: 1024,
expected: "1.0 KB",
},
{
name: "1.5 KB",
bytes: 1536,
expected: "1.5 KB",
},
{
name: "1 MB",
bytes: 1024 * 1024,
expected: "1.0 MB",
},
{
name: "2.5 MB",
bytes: 2621440, // 2.5 * 1024 * 1024
expected: "2.5 MB",
},
{
name: "1 GB",
bytes: 1024 * 1024 * 1024,
expected: "1.0 GB",
},
{
name: "5.2 GB",
bytes: 5583457485, // ~5.2 GB
expected: "5.2 GB",
},
{
name: "1 TB",
bytes: 1024 * 1024 * 1024 * 1024,
expected: "1.0 TB",
},
{
name: "1.5 PB",
bytes: 1688849860263936, // 1.5 PB
expected: "1.5 PB",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ humanizeBytes . }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
err = temp.Execute(buf, tt.bytes)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("humanizeBytes(%d) = %q, want %q", tt.bytes, got, tt.expected)
}
})
}
}
func TestTruncateDigest(t *testing.T) {
tests := []struct {
name string
digest string
length int
expected string
}{
{
name: "short digest - no truncation needed",
digest: "sha256:abc",
length: 20,
expected: "sha256:abc",
},
{
name: "truncate to 12 chars",
digest: "sha256:abcdef123456789",
length: 12,
expected: "sha256:abcde...",
},
{
name: "truncate to 8 chars",
digest: "sha256:1234567890abcdef",
length: 8,
expected: "sha256:1...",
},
{
name: "exact length match",
digest: "sha256:abc",
length: 10,
expected: "sha256:abc",
},
{
name: "empty digest",
digest: "",
length: 10,
expected: "",
},
{
name: "long sha256 digest",
digest: "sha256:f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1",
length: 16,
expected: "sha256:f1c8f6a4b...",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ truncateDigest .Digest .Length }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
data := struct {
Digest string
Length int
}{
Digest: tt.digest,
Length: tt.length,
}
err = temp.Execute(buf, data)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("truncateDigest(%q, %d) = %q, want %q", tt.digest, tt.length, got, tt.expected)
}
})
}
}
func TestFirstChar(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "normal string",
input: "hello",
expected: "h",
},
{
name: "uppercase",
input: "World",
expected: "W",
},
{
name: "single character",
input: "a",
expected: "a",
},
{
name: "empty string",
input: "",
expected: "?",
},
{
name: "unicode character",
input: "😀 emoji",
expected: "😀",
},
{
name: "chinese character",
input: "你好",
expected: "你",
},
{
name: "number",
input: "123",
expected: "1",
},
{
name: "special character",
input: "@user",
expected: "@",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ firstChar . }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
err = temp.Execute(buf, tt.input)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("firstChar(%q) = %q, want %q", tt.input, got, tt.expected)
}
})
}
}
func TestTrimPrefix(t *testing.T) {
tests := []struct {
name string
prefix string
input string
expected string
}{
{
name: "trim sha256 prefix",
prefix: "sha256:",
input: "sha256:abcdef123456",
expected: "abcdef123456",
},
{
name: "no prefix match",
prefix: "sha256:",
input: "md5:abcdef123456",
expected: "md5:abcdef123456",
},
{
name: "empty prefix",
prefix: "",
input: "hello",
expected: "hello",
},
{
name: "empty string",
prefix: "prefix:",
input: "",
expected: "",
},
{
name: "prefix longer than string",
prefix: "very-long-prefix",
input: "short",
expected: "short",
},
{
name: "exact match",
prefix: "prefix",
input: "prefix",
expected: "",
},
{
name: "partial prefix match",
prefix: "sha256:",
input: "sha25",
expected: "sha25",
},
{
name: "trim docker.io prefix",
prefix: "docker.io/",
input: "docker.io/library/alpine",
expected: "library/alpine",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ trimPrefix .Prefix .Input }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
data := struct {
Prefix string
Input string
}{
Prefix: tt.prefix,
Input: tt.input,
}
err = temp.Execute(buf, data)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("trimPrefix(%q, %q) = %q, want %q", tt.prefix, tt.input, got, tt.expected)
}
})
}
}
func TestSanitizeID(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "digest with colon",
input: "sha256:abc123",
expected: "sha256-abc123",
},
{
name: "full digest",
input: "sha256:f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1",
expected: "sha256-f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1",
},
{
name: "multiple colons",
input: "sha256:abc:def:ghi",
expected: "sha256-abc-def-ghi",
},
{
name: "no colons",
input: "abcdef123456",
expected: "abcdef123456",
},
{
name: "empty string",
input: "",
expected: "",
},
{
name: "only colon",
input: ":",
expected: "-",
},
{
name: "leading colon",
input: ":abc",
expected: "-abc",
},
{
name: "trailing colon",
input: "abc:",
expected: "abc-",
},
{
name: "version tag with periods",
input: "v0.0.2",
expected: "v0-0-2",
},
{
name: "colons and periods",
input: "sha256:abc.def",
expected: "sha256-abc-def",
},
{
name: "only period",
input: ".",
expected: "-",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ sanitizeID . }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
err = temp.Execute(buf, tt.input)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
if got != tt.expected {
t.Errorf("sanitizeID(%q) = %q, want %q", tt.input, got, tt.expected)
}
})
}
}
func TestTemplates(t *testing.T) {
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
if tmpl == nil {
t.Fatal("Templates(nil) returned nil template")
}
// Test that all expected templates are loaded
expectedTemplates := []string{
"nav",
"repo-card",
"repository",
"home.html",
"search.html",
"user.html",
"login.html",
"settings.html",
"install.html",
"manifest-modal",
"search-results.html",
"health-badge",
"alert",
}
for _, name := range expectedTemplates {
t.Run("template_"+name, func(t *testing.T) {
temp := tmpl.Lookup(name)
if temp == nil {
t.Errorf("Expected template %q not found", name)
}
})
}
}
func TestTemplateExecution_RepoCard(t *testing.T) {
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
// Sample data for repo-card template
data := struct {
OwnerHandle string
OwnerAvatarURL string
Repository string
IconURL string
Description string
StarCount int
PullCount int
IsStarred bool
ArtifactType string
Tag string
Digest string
LastUpdated time.Time
RegistryURL string
OciClient string
}{
OwnerHandle: "alice.bsky.social",
OwnerAvatarURL: "",
Repository: "myapp",
IconURL: "",
Description: "A cool container image",
StarCount: 42,
PullCount: 1337,
IsStarred: true,
ArtifactType: "container-image",
Tag: "latest",
Digest: "sha256:abc123def456",
LastUpdated: time.Now().Add(-24 * time.Hour),
RegistryURL: "atcr.io",
}
buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, "repo-card", data)
if err != nil {
t.Fatalf("Failed to execute repo-card template: %v", err)
}
output := buf.String()
// Verify expected content in output
expectedContent := []string{
"alice.bsky.social",
"myapp",
"A cool container image",
"42", // star count
"1337", // pull count
"avatar-placeholder", // DaisyUI avatar placeholder when no icon URL
}
for _, expected := range expectedContent {
if !strings.Contains(output, expected) {
t.Errorf("Template output missing expected content %q", expected)
}
}
// Verify firstChar function is working
if !strings.Contains(output, ">m<") { // first char of "myapp"
t.Error("Template output missing firstChar result")
}
}
func TestTemplateExecution_WithFuncMap(t *testing.T) {
// Test that templates can use FuncMap functions
tests := []struct {
name string
templateStr string
data any
expectInOutput string
}{
{
name: "timeAgo in template",
templateStr: `{{ define "test1" }}{{ timeAgo . }}{{ end }}`,
data: time.Now().Add(-5 * time.Minute),
expectInOutput: "5 minutes ago",
},
{
name: "humanizeBytes in template",
templateStr: `{{ define "test2" }}{{ humanizeBytes . }}{{ end }}`,
data: int64(1024 * 1024 * 10), // 10 MB
expectInOutput: "10.0 MB",
},
{
name: "multiple functions in template",
templateStr: `{{ define "test3" }}{{ truncateDigest .Digest 12 }} - {{ firstChar .Name }}{{ end }}`,
data: struct {
Digest string
Name string
}{
Digest: "sha256:abcdef1234567890",
Name: "myapp",
},
expectInOutput: "sha256:abcde... - m",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Get fresh template for each test case
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
temp, err := tmpl.Parse(tt.templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
buf := new(bytes.Buffer)
// Extract the template name from the define
templateName := strings.Split(strings.TrimPrefix(tt.templateStr, `{{ define "`), `"`)[0]
err = temp.ExecuteTemplate(buf, templateName, tt.data)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
output := buf.String()
if !strings.Contains(output, tt.expectInOutput) {
t.Errorf("Template output %q does not contain expected %q", output, tt.expectInOutput)
}
})
}
}
func TestTemplateExecution_HealthBadge(t *testing.T) {
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
tests := []struct {
name string
data map[string]any
expectInOutput string
expectMissing string
}{
{
name: "pending state",
data: map[string]any{
"Pending": true,
"Reachable": false,
"RetryURL": "http%3A%2F%2Fexample.com",
},
expectInOutput: "badge-info",
expectMissing: "badge-warning",
},
{
name: "offline state",
data: map[string]any{
"Pending": false,
"Reachable": false,
"RetryURL": "",
},
expectInOutput: "badge-warning",
expectMissing: "badge-info",
},
{
name: "online state - empty output",
data: map[string]any{
"Pending": false,
"Reachable": true,
"RetryURL": "",
},
expectMissing: "badge",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := new(bytes.Buffer)
err := tmpl.ExecuteTemplate(buf, "health-badge", tt.data)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
output := buf.String()
if tt.expectInOutput != "" && !strings.Contains(output, tt.expectInOutput) {
t.Errorf("Template output %q does not contain expected %q", output, tt.expectInOutput)
}
if tt.expectMissing != "" && strings.Contains(output, tt.expectMissing) {
t.Errorf("Template output %q should not contain %q", output, tt.expectMissing)
}
})
}
}
func TestTemplateExecution_Alert(t *testing.T) {
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
data := map[string]string{
"Type": "success",
"Message": "Operation completed!",
}
buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, "alert", data)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
output := buf.String()
expectedParts := []string{"success", "check", "Operation completed!"}
for _, expected := range expectedParts {
if !strings.Contains(output, expected) {
t.Errorf("Template output %q does not contain expected %q", output, expected)
}
}
}
func TestPublicHandler(t *testing.T) {
handler := PublicHandler(nil)
if handler == nil {
t.Fatal("StaticHandler() returned nil")
}
// Test that it returns an http.Handler
// Further testing would require HTTP request/response testing
// which is typically done in integration tests
}
func TestJSONLDScript(t *testing.T) {
tests := []struct {
name string
input any
expectContains []string
expectMissing []string
}{
{
name: "struct input - renders script block with JSON",
input: struct {
Context string `json:"@context"`
Type string `json:"@type"`
Name string `json:"name"`
}{
Context: "https://schema.org",
Type: "Organization",
Name: "ATCR",
},
expectContains: []string{
`<script type="application/ld+json">`,
`"@context": "https://schema.org"`,
`"@type": "Organization"`,
`"name": "ATCR"`,
`</script>`,
},
expectMissing: []string{
`&#34;`, // Should NOT contain HTML-escaped quotes
},
},
{
name: "string input - returns as-is in script block",
input: `{"@context": "https://schema.org", "@type": "Thing"}`,
expectContains: []string{
`<script type="application/ld+json">`,
`{"@context": "https://schema.org", "@type": "Thing"}`,
`</script>`,
},
},
{
name: "nested struct - proper JSON nesting",
input: struct {
Context string `json:"@context"`
Author struct {
Type string `json:"@type"`
Name string `json:"name"`
} `json:"author"`
}{
Context: "https://schema.org",
Author: struct {
Type string `json:"@type"`
Name string `json:"name"`
}{
Type: "Person",
Name: "Alice",
},
},
expectContains: []string{
`"@context": "https://schema.org"`,
`"author": {`,
`"@type": "Person"`,
`"name": "Alice"`,
},
},
{
name: "empty struct - returns empty JSON object in script block",
input: struct{}{},
expectContains: []string{
`<script type="application/ld+json">`,
`{}`,
`</script>`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl, err := Templates(nil)
if err != nil {
t.Fatalf("Templates(nil) error = %v", err)
}
templateStr := `{{ jsonldScript . }}`
buf := new(bytes.Buffer)
temp, err := tmpl.New("test").Parse(templateStr)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
err = temp.Execute(buf, tt.input)
if err != nil {
t.Fatalf("Failed to execute template: %v", err)
}
got := buf.String()
for _, expected := range tt.expectContains {
if !strings.Contains(got, expected) {
t.Errorf("jsonldScript output missing expected %q\nGot: %s", expected, got)
}
}
for _, notExpected := range tt.expectMissing {
if strings.Contains(got, notExpected) {
t.Errorf("jsonldScript output should not contain %q\nGot: %s", notExpected, got)
}
}
})
}
}