A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at refactor 693 lines 15 kB view raw
1package appview 2 3import ( 4 "bytes" 5 "strings" 6 "testing" 7 "time" 8) 9 10func TestTimeAgo(t *testing.T) { 11 now := time.Now() 12 13 tests := []struct { 14 name string 15 time time.Time 16 expected string 17 }{ 18 { 19 name: "just now - 30 seconds ago", 20 time: now.Add(-30 * time.Second), 21 expected: "just now", 22 }, 23 { 24 name: "1 minute ago", 25 time: now.Add(-1 * time.Minute), 26 expected: "1 minute ago", 27 }, 28 { 29 name: "5 minutes ago", 30 time: now.Add(-5 * time.Minute), 31 expected: "5 minutes ago", 32 }, 33 { 34 name: "45 minutes ago", 35 time: now.Add(-45 * time.Minute), 36 expected: "45 minutes ago", 37 }, 38 { 39 name: "1 hour ago", 40 time: now.Add(-1 * time.Hour), 41 expected: "1 hour ago", 42 }, 43 { 44 name: "3 hours ago", 45 time: now.Add(-3 * time.Hour), 46 expected: "3 hours ago", 47 }, 48 { 49 name: "23 hours ago", 50 time: now.Add(-23 * time.Hour), 51 expected: "23 hours ago", 52 }, 53 { 54 name: "1 day ago", 55 time: now.Add(-24 * time.Hour), 56 expected: "1 day ago", 57 }, 58 { 59 name: "5 days ago", 60 time: now.Add(-5 * 24 * time.Hour), 61 expected: "5 days ago", 62 }, 63 { 64 name: "30 days ago", 65 time: now.Add(-30 * 24 * time.Hour), 66 expected: "30 days ago", 67 }, 68 } 69 70 for _, tt := range tests { 71 t.Run(tt.name, func(t *testing.T) { 72 // Get fresh template for each test case 73 tmpl, err := Templates() 74 if err != nil { 75 t.Fatalf("Templates() error = %v", err) 76 } 77 78 // Execute template using timeAgo function 79 templateStr := `{{ timeAgo . }}` 80 buf := new(bytes.Buffer) 81 temp, err := tmpl.New("test").Parse(templateStr) 82 if err != nil { 83 t.Fatalf("Failed to parse template: %v", err) 84 } 85 86 err = temp.Execute(buf, tt.time) 87 if err != nil { 88 t.Fatalf("Failed to execute template: %v", err) 89 } 90 91 got := buf.String() 92 if got != tt.expected { 93 t.Errorf("timeAgo() = %q, want %q", got, tt.expected) 94 } 95 }) 96 } 97} 98 99func TestHumanizeBytes(t *testing.T) { 100 tests := []struct { 101 name string 102 bytes int64 103 expected string 104 }{ 105 { 106 name: "0 bytes", 107 bytes: 0, 108 expected: "0 B", 109 }, 110 { 111 name: "512 bytes", 112 bytes: 512, 113 expected: "512 B", 114 }, 115 { 116 name: "1023 bytes", 117 bytes: 1023, 118 expected: "1023 B", 119 }, 120 { 121 name: "1 KB", 122 bytes: 1024, 123 expected: "1.0 KB", 124 }, 125 { 126 name: "1.5 KB", 127 bytes: 1536, 128 expected: "1.5 KB", 129 }, 130 { 131 name: "1 MB", 132 bytes: 1024 * 1024, 133 expected: "1.0 MB", 134 }, 135 { 136 name: "2.5 MB", 137 bytes: 2621440, // 2.5 * 1024 * 1024 138 expected: "2.5 MB", 139 }, 140 { 141 name: "1 GB", 142 bytes: 1024 * 1024 * 1024, 143 expected: "1.0 GB", 144 }, 145 { 146 name: "5.2 GB", 147 bytes: 5583457485, // ~5.2 GB 148 expected: "5.2 GB", 149 }, 150 { 151 name: "1 TB", 152 bytes: 1024 * 1024 * 1024 * 1024, 153 expected: "1.0 TB", 154 }, 155 { 156 name: "1.5 PB", 157 bytes: 1688849860263936, // 1.5 PB 158 expected: "1.5 PB", 159 }, 160 } 161 162 for _, tt := range tests { 163 t.Run(tt.name, func(t *testing.T) { 164 // Get fresh template for each test case 165 tmpl, err := Templates() 166 if err != nil { 167 t.Fatalf("Templates() error = %v", err) 168 } 169 170 templateStr := `{{ humanizeBytes . }}` 171 buf := new(bytes.Buffer) 172 temp, err := tmpl.New("test").Parse(templateStr) 173 if err != nil { 174 t.Fatalf("Failed to parse template: %v", err) 175 } 176 177 err = temp.Execute(buf, tt.bytes) 178 if err != nil { 179 t.Fatalf("Failed to execute template: %v", err) 180 } 181 182 got := buf.String() 183 if got != tt.expected { 184 t.Errorf("humanizeBytes(%d) = %q, want %q", tt.bytes, got, tt.expected) 185 } 186 }) 187 } 188} 189 190func TestTruncateDigest(t *testing.T) { 191 tests := []struct { 192 name string 193 digest string 194 length int 195 expected string 196 }{ 197 { 198 name: "short digest - no truncation needed", 199 digest: "sha256:abc", 200 length: 20, 201 expected: "sha256:abc", 202 }, 203 { 204 name: "truncate to 12 chars", 205 digest: "sha256:abcdef123456789", 206 length: 12, 207 expected: "sha256:abcde...", 208 }, 209 { 210 name: "truncate to 8 chars", 211 digest: "sha256:1234567890abcdef", 212 length: 8, 213 expected: "sha256:1...", 214 }, 215 { 216 name: "exact length match", 217 digest: "sha256:abc", 218 length: 10, 219 expected: "sha256:abc", 220 }, 221 { 222 name: "empty digest", 223 digest: "", 224 length: 10, 225 expected: "", 226 }, 227 { 228 name: "long sha256 digest", 229 digest: "sha256:f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1", 230 length: 16, 231 expected: "sha256:f1c8f6a4b...", 232 }, 233 } 234 235 for _, tt := range tests { 236 t.Run(tt.name, func(t *testing.T) { 237 // Get fresh template for each test case 238 tmpl, err := Templates() 239 if err != nil { 240 t.Fatalf("Templates() error = %v", err) 241 } 242 243 templateStr := `{{ truncateDigest .Digest .Length }}` 244 buf := new(bytes.Buffer) 245 temp, err := tmpl.New("test").Parse(templateStr) 246 if err != nil { 247 t.Fatalf("Failed to parse template: %v", err) 248 } 249 250 data := struct { 251 Digest string 252 Length int 253 }{ 254 Digest: tt.digest, 255 Length: tt.length, 256 } 257 258 err = temp.Execute(buf, data) 259 if err != nil { 260 t.Fatalf("Failed to execute template: %v", err) 261 } 262 263 got := buf.String() 264 if got != tt.expected { 265 t.Errorf("truncateDigest(%q, %d) = %q, want %q", tt.digest, tt.length, got, tt.expected) 266 } 267 }) 268 } 269} 270 271func TestFirstChar(t *testing.T) { 272 tests := []struct { 273 name string 274 input string 275 expected string 276 }{ 277 { 278 name: "normal string", 279 input: "hello", 280 expected: "h", 281 }, 282 { 283 name: "uppercase", 284 input: "World", 285 expected: "W", 286 }, 287 { 288 name: "single character", 289 input: "a", 290 expected: "a", 291 }, 292 { 293 name: "empty string", 294 input: "", 295 expected: "?", 296 }, 297 { 298 name: "unicode character", 299 input: "😀 emoji", 300 expected: "😀", 301 }, 302 { 303 name: "chinese character", 304 input: "你好", 305 expected: "你", 306 }, 307 { 308 name: "number", 309 input: "123", 310 expected: "1", 311 }, 312 { 313 name: "special character", 314 input: "@user", 315 expected: "@", 316 }, 317 } 318 319 for _, tt := range tests { 320 t.Run(tt.name, func(t *testing.T) { 321 // Get fresh template for each test case 322 tmpl, err := Templates() 323 if err != nil { 324 t.Fatalf("Templates() error = %v", err) 325 } 326 327 templateStr := `{{ firstChar . }}` 328 buf := new(bytes.Buffer) 329 temp, err := tmpl.New("test").Parse(templateStr) 330 if err != nil { 331 t.Fatalf("Failed to parse template: %v", err) 332 } 333 334 err = temp.Execute(buf, tt.input) 335 if err != nil { 336 t.Fatalf("Failed to execute template: %v", err) 337 } 338 339 got := buf.String() 340 if got != tt.expected { 341 t.Errorf("firstChar(%q) = %q, want %q", tt.input, got, tt.expected) 342 } 343 }) 344 } 345} 346 347func TestTrimPrefix(t *testing.T) { 348 tests := []struct { 349 name string 350 prefix string 351 input string 352 expected string 353 }{ 354 { 355 name: "trim sha256 prefix", 356 prefix: "sha256:", 357 input: "sha256:abcdef123456", 358 expected: "abcdef123456", 359 }, 360 { 361 name: "no prefix match", 362 prefix: "sha256:", 363 input: "md5:abcdef123456", 364 expected: "md5:abcdef123456", 365 }, 366 { 367 name: "empty prefix", 368 prefix: "", 369 input: "hello", 370 expected: "hello", 371 }, 372 { 373 name: "empty string", 374 prefix: "prefix:", 375 input: "", 376 expected: "", 377 }, 378 { 379 name: "prefix longer than string", 380 prefix: "very-long-prefix", 381 input: "short", 382 expected: "short", 383 }, 384 { 385 name: "exact match", 386 prefix: "prefix", 387 input: "prefix", 388 expected: "", 389 }, 390 { 391 name: "partial prefix match", 392 prefix: "sha256:", 393 input: "sha25", 394 expected: "sha25", 395 }, 396 { 397 name: "trim docker.io prefix", 398 prefix: "docker.io/", 399 input: "docker.io/library/alpine", 400 expected: "library/alpine", 401 }, 402 } 403 404 for _, tt := range tests { 405 t.Run(tt.name, func(t *testing.T) { 406 // Get fresh template for each test case 407 tmpl, err := Templates() 408 if err != nil { 409 t.Fatalf("Templates() error = %v", err) 410 } 411 412 templateStr := `{{ trimPrefix .Prefix .Input }}` 413 buf := new(bytes.Buffer) 414 temp, err := tmpl.New("test").Parse(templateStr) 415 if err != nil { 416 t.Fatalf("Failed to parse template: %v", err) 417 } 418 419 data := struct { 420 Prefix string 421 Input string 422 }{ 423 Prefix: tt.prefix, 424 Input: tt.input, 425 } 426 427 err = temp.Execute(buf, data) 428 if err != nil { 429 t.Fatalf("Failed to execute template: %v", err) 430 } 431 432 got := buf.String() 433 if got != tt.expected { 434 t.Errorf("trimPrefix(%q, %q) = %q, want %q", tt.prefix, tt.input, got, tt.expected) 435 } 436 }) 437 } 438} 439 440func TestSanitizeID(t *testing.T) { 441 tests := []struct { 442 name string 443 input string 444 expected string 445 }{ 446 { 447 name: "digest with colon", 448 input: "sha256:abc123", 449 expected: "sha256-abc123", 450 }, 451 { 452 name: "full digest", 453 input: "sha256:f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1", 454 expected: "sha256-f1c8f6a4b7e9d2c0a3f5b8e1d4c7a0b3e6f9c2d5a8b1e4f7c0d3a6b9e2f5c8a1", 455 }, 456 { 457 name: "multiple colons", 458 input: "sha256:abc:def:ghi", 459 expected: "sha256-abc-def-ghi", 460 }, 461 { 462 name: "no colons", 463 input: "abcdef123456", 464 expected: "abcdef123456", 465 }, 466 { 467 name: "empty string", 468 input: "", 469 expected: "", 470 }, 471 { 472 name: "only colon", 473 input: ":", 474 expected: "-", 475 }, 476 { 477 name: "leading colon", 478 input: ":abc", 479 expected: "-abc", 480 }, 481 { 482 name: "trailing colon", 483 input: "abc:", 484 expected: "abc-", 485 }, 486 { 487 name: "version tag with periods", 488 input: "v0.0.2", 489 expected: "v0-0-2", 490 }, 491 { 492 name: "colons and periods", 493 input: "sha256:abc.def", 494 expected: "sha256-abc-def", 495 }, 496 { 497 name: "only period", 498 input: ".", 499 expected: "-", 500 }, 501 } 502 503 for _, tt := range tests { 504 t.Run(tt.name, func(t *testing.T) { 505 // Get fresh template for each test case 506 tmpl, err := Templates() 507 if err != nil { 508 t.Fatalf("Templates() error = %v", err) 509 } 510 511 templateStr := `{{ sanitizeID . }}` 512 buf := new(bytes.Buffer) 513 temp, err := tmpl.New("test").Parse(templateStr) 514 if err != nil { 515 t.Fatalf("Failed to parse template: %v", err) 516 } 517 518 err = temp.Execute(buf, tt.input) 519 if err != nil { 520 t.Fatalf("Failed to execute template: %v", err) 521 } 522 523 got := buf.String() 524 if got != tt.expected { 525 t.Errorf("sanitizeID(%q) = %q, want %q", tt.input, got, tt.expected) 526 } 527 }) 528 } 529} 530 531func TestTemplates(t *testing.T) { 532 tmpl, err := Templates() 533 if err != nil { 534 t.Fatalf("Templates() error = %v", err) 535 } 536 537 if tmpl == nil { 538 t.Fatal("Templates() returned nil template") 539 } 540 541 // Test that all expected templates are loaded 542 expectedTemplates := []string{ 543 "nav", 544 "repo-card", 545 "repository", 546 "home.html", 547 "search.html", 548 "user.html", 549 "login.html", 550 "settings.html", 551 "install.html", 552 "manifest-modal", 553 "push-list.html", 554 } 555 556 for _, name := range expectedTemplates { 557 t.Run("template_"+name, func(t *testing.T) { 558 temp := tmpl.Lookup(name) 559 if temp == nil { 560 t.Errorf("Expected template %q not found", name) 561 } 562 }) 563 } 564} 565 566func TestTemplateExecution_RepoCard(t *testing.T) { 567 tmpl, err := Templates() 568 if err != nil { 569 t.Fatalf("Templates() error = %v", err) 570 } 571 572 // Sample data for repo-card template 573 data := struct { 574 OwnerHandle string 575 Repository string 576 IconURL string 577 Description string 578 StarCount int 579 PullCount int 580 IsStarred bool 581 }{ 582 OwnerHandle: "alice.bsky.social", 583 Repository: "myapp", 584 IconURL: "", 585 Description: "A cool container image", 586 StarCount: 42, 587 PullCount: 1337, 588 IsStarred: true, 589 } 590 591 buf := new(bytes.Buffer) 592 err = tmpl.ExecuteTemplate(buf, "repo-card", data) 593 if err != nil { 594 t.Fatalf("Failed to execute repo-card template: %v", err) 595 } 596 597 output := buf.String() 598 599 // Verify expected content in output 600 expectedContent := []string{ 601 "alice.bsky.social", 602 "myapp", 603 "A cool container image", 604 "42", // star count 605 "1337", // pull count 606 "featured-icon-placeholder", // no icon URL provided 607 } 608 609 for _, expected := range expectedContent { 610 if !strings.Contains(output, expected) { 611 t.Errorf("Template output missing expected content %q", expected) 612 } 613 } 614 615 // Verify firstChar function is working 616 if !strings.Contains(output, ">m<") { // first char of "myapp" 617 t.Error("Template output missing firstChar result") 618 } 619} 620 621func TestTemplateExecution_WithFuncMap(t *testing.T) { 622 // Test that templates can use FuncMap functions 623 tests := []struct { 624 name string 625 templateStr string 626 data any 627 expectInOutput string 628 }{ 629 { 630 name: "timeAgo in template", 631 templateStr: `{{ define "test1" }}{{ timeAgo . }}{{ end }}`, 632 data: time.Now().Add(-5 * time.Minute), 633 expectInOutput: "5 minutes ago", 634 }, 635 { 636 name: "humanizeBytes in template", 637 templateStr: `{{ define "test2" }}{{ humanizeBytes . }}{{ end }}`, 638 data: int64(1024 * 1024 * 10), // 10 MB 639 expectInOutput: "10.0 MB", 640 }, 641 { 642 name: "multiple functions in template", 643 templateStr: `{{ define "test3" }}{{ truncateDigest .Digest 12 }} - {{ firstChar .Name }}{{ end }}`, 644 data: struct { 645 Digest string 646 Name string 647 }{ 648 Digest: "sha256:abcdef1234567890", 649 Name: "myapp", 650 }, 651 expectInOutput: "sha256:abcde... - m", 652 }, 653 } 654 655 for _, tt := range tests { 656 t.Run(tt.name, func(t *testing.T) { 657 // Get fresh template for each test case 658 tmpl, err := Templates() 659 if err != nil { 660 t.Fatalf("Templates() error = %v", err) 661 } 662 663 temp, err := tmpl.Parse(tt.templateStr) 664 if err != nil { 665 t.Fatalf("Failed to parse template: %v", err) 666 } 667 668 buf := new(bytes.Buffer) 669 // Extract the template name from the define 670 templateName := strings.Split(strings.TrimPrefix(tt.templateStr, `{{ define "`), `"`)[0] 671 err = temp.ExecuteTemplate(buf, templateName, tt.data) 672 if err != nil { 673 t.Fatalf("Failed to execute template: %v", err) 674 } 675 676 output := buf.String() 677 if !strings.Contains(output, tt.expectInOutput) { 678 t.Errorf("Template output %q does not contain expected %q", output, tt.expectInOutput) 679 } 680 }) 681 } 682} 683 684func TestStaticHandler(t *testing.T) { 685 handler := StaticHandler() 686 if handler == nil { 687 t.Fatal("StaticHandler() returned nil") 688 } 689 690 // Test that it returns an http.Handler 691 // Further testing would require HTTP request/response testing 692 // which is typically done in integration tests 693}