679 lines
14 KiB
Go
679 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-",
|
|
},
|
|
}
|
|
|
|
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
|
|
}
|