Files
at-container-registry/pkg/appview/ui_test.go
2025-11-24 13:51:00 -06:00

694 lines
14 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() 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()
if err != nil {
t.Fatalf("Templates() error = %v", err)
}
if tmpl == nil {
t.Fatal("Templates() 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",
"push-list.html",
}
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()
if err != nil {
t.Fatalf("Templates() error = %v", err)
}
// Sample data for repo-card template
data := struct {
OwnerHandle string
Repository string
IconURL string
Description string
StarCount int
PullCount int
IsStarred bool
}{
OwnerHandle: "alice.bsky.social",
Repository: "myapp",
IconURL: "",
Description: "A cool container image",
StarCount: 42,
PullCount: 1337,
IsStarred: true,
}
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
"featured-icon-placeholder", // no icon URL provided
}
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()
if err != nil {
t.Fatalf("Templates() 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 TestStaticHandler(t *testing.T) {
handler := StaticHandler()
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
}