blob_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package git
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. )
  8. func TestPreviewMimeType(t *testing.T) {
  9. tests := []struct {
  10. name string
  11. path string
  12. want string
  13. }{
  14. {name: "png", path: "img/icon.png", want: "image/png"},
  15. {name: "jpg", path: "photo.jpg", want: "image/jpeg"},
  16. {name: "jpeg", path: "photo.jpeg", want: "image/jpeg"},
  17. {name: "uppercase extension", path: "PHOTO.JPG", want: "image/jpeg"},
  18. {name: "gif", path: "a.gif", want: "image/gif"},
  19. {name: "webp", path: "a.webp", want: "image/webp"},
  20. {name: "svg", path: "a.svg", want: "image/svg+xml"},
  21. {name: "pdf", path: "docs/manual.pdf", want: "application/pdf"},
  22. {name: "mp4", path: "clip.mp4", want: "video/mp4"},
  23. {name: "webm", path: "clip.webm", want: "video/webm"},
  24. {name: "mp3", path: "song.mp3", want: "audio/mpeg"},
  25. {name: "flac", path: "song.flac", want: "audio/flac"},
  26. {name: "go source", path: "main.go", want: ""},
  27. {name: "no extension", path: "Makefile", want: ""},
  28. {name: "dotfile", path: ".gitignore", want: ""},
  29. {name: "unknown extension", path: "a.xyz", want: ""},
  30. {name: "double extension uses the last", path: "archive.png.gz", want: ""},
  31. {name: "path with folders", path: "a/b/c/photo.png", want: "image/png"},
  32. }
  33. for _, test := range tests {
  34. t.Run(test.name, func(t *testing.T) {
  35. if got := PreviewMimeType(test.path); got != test.want {
  36. t.Errorf("PreviewMimeType(%q) = %q, want %q", test.path, got, test.want)
  37. }
  38. })
  39. }
  40. }
  41. func TestPreviewKind(t *testing.T) {
  42. tests := []struct {
  43. name string
  44. path string
  45. want string
  46. }{
  47. {name: "png is an image", path: "a.png", want: "image"},
  48. {name: "svg is an image", path: "a.svg", want: "image"},
  49. {name: "pdf is its own kind", path: "a.pdf", want: "pdf"},
  50. {name: "mp4 is video", path: "a.mp4", want: "video"},
  51. {name: "mp3 is audio", path: "a.mp3", want: "audio"},
  52. {name: "source file has no preview", path: "a.go", want: ""},
  53. {name: "no extension", path: "LICENSE", want: ""},
  54. }
  55. for _, test := range tests {
  56. t.Run(test.name, func(t *testing.T) {
  57. if got := PreviewKind(test.path); got != test.want {
  58. t.Errorf("PreviewKind(%q) = %q, want %q", test.path, got, test.want)
  59. }
  60. })
  61. }
  62. }
  63. func TestFileBlobAtHead(t *testing.T) {
  64. manager := newTestManager(t)
  65. repoPath := newTestRepo(t, manager)
  66. commitFile(t, manager, repoPath, "a.txt", "committed content\n", "first")
  67. //Change the working tree: the blob must still be the committed version
  68. writeFile(t, repoPath, "a.txt", "working tree content\n")
  69. content, exists, err := manager.FileBlob(repoPath, "a.txt", "HEAD")
  70. if err != nil {
  71. t.Fatalf("FileBlob() returned error: %v", err)
  72. }
  73. if !exists {
  74. t.Fatalf("exists = false for a committed file, want true")
  75. }
  76. if string(content) != "committed content\n" {
  77. t.Errorf("FileBlob() = %q, want the committed content", string(content))
  78. }
  79. }
  80. func TestFileBlobRevisionForms(t *testing.T) {
  81. manager := newTestManager(t)
  82. repoPath := newTestRepo(t, manager)
  83. first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  84. commitFile(t, manager, repoPath, "a.txt", "two\n", "second")
  85. tests := []struct {
  86. name string
  87. revision string
  88. want string
  89. }{
  90. {name: "explicit HEAD", revision: "HEAD", want: "two\n"},
  91. {name: "lowercase head", revision: "head", want: "two\n"},
  92. {name: "empty means HEAD", revision: "", want: "two\n"},
  93. {name: "whitespace means HEAD", revision: " ", want: "two\n"},
  94. {name: "explicit commit hash", revision: first, want: "one\n"},
  95. }
  96. for _, test := range tests {
  97. t.Run(test.name, func(t *testing.T) {
  98. content, exists, err := manager.FileBlob(repoPath, "a.txt", test.revision)
  99. if err != nil {
  100. t.Fatalf("FileBlob(%q) returned error: %v", test.revision, err)
  101. }
  102. if !exists {
  103. t.Fatalf("exists = false, want true")
  104. }
  105. if string(content) != test.want {
  106. t.Errorf("FileBlob(%q) = %q, want %q", test.revision, string(content), test.want)
  107. }
  108. })
  109. }
  110. }
  111. func TestFileBlobMissingPathIsNotAnError(t *testing.T) {
  112. manager := newTestManager(t)
  113. repoPath := newTestRepo(t, manager)
  114. commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  115. content, exists, err := manager.FileBlob(repoPath, "never-committed.png", "HEAD")
  116. if err != nil {
  117. t.Fatalf("FileBlob() on a missing path returned error: %v", err)
  118. }
  119. if exists {
  120. t.Errorf("exists = true for a path that was never committed, want false")
  121. }
  122. if content != nil {
  123. t.Errorf("content = %q, want nil", string(content))
  124. }
  125. }
  126. func TestFileBlobOnUnbornBranch(t *testing.T) {
  127. manager := newTestManager(t)
  128. repoPath := newTestRepo(t, manager)
  129. writeFile(t, repoPath, "new.png", "not committed yet")
  130. content, exists, err := manager.FileBlob(repoPath, "new.png", "HEAD")
  131. if err != nil {
  132. t.Fatalf("FileBlob() on an unborn branch returned error: %v", err)
  133. }
  134. if exists {
  135. t.Errorf("exists = true before any commit, want false")
  136. }
  137. if content != nil {
  138. t.Errorf("content = %q, want nil", string(content))
  139. }
  140. }
  141. func TestFileBlobValidation(t *testing.T) {
  142. manager := newTestManager(t)
  143. repoPath := newTestRepo(t, manager)
  144. commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
  145. tests := []struct {
  146. name string
  147. path string
  148. file string
  149. revision string
  150. }{
  151. {name: "not a repository", path: t.TempDir(), file: "a.txt", revision: "HEAD"},
  152. {name: "escaping path", path: repoPath, file: "../../secret.txt", revision: "HEAD"},
  153. {name: "absolute path", path: repoPath, file: "/absolute/a.txt", revision: "HEAD"},
  154. {name: "empty file", path: repoPath, file: "", revision: "HEAD"},
  155. {name: "short hash rejected", path: repoPath, file: "a.txt", revision: "abc1234"},
  156. {name: "non hex revision", path: repoPath, file: "a.txt", revision: "not-a-hash"},
  157. {name: "branch name rejected", path: repoPath, file: "a.txt", revision: "master"},
  158. {name: "unknown commit", path: repoPath, file: "a.txt", revision: strings.Repeat("a", 40)},
  159. }
  160. for _, test := range tests {
  161. t.Run(test.name, func(t *testing.T) {
  162. if _, _, err := manager.FileBlob(test.path, test.file, test.revision); err == nil {
  163. t.Errorf("FileBlob() with %s = nil error, want an error", test.name)
  164. }
  165. })
  166. }
  167. }
  168. // TestFileBlobBinaryRoundTrip makes sure image bytes survive intact — a preview
  169. // is worthless if the content is mangled on the way out.
  170. func TestFileBlobBinaryRoundTrip(t *testing.T) {
  171. manager := newTestManager(t)
  172. repoPath := newTestRepo(t, manager)
  173. //A tiny valid PNG header followed by bytes that would break text handling
  174. original := []byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0xFF, 0xFE, 0x00, 0x0D}
  175. if err := os.WriteFile(filepath.Join(repoPath, "icon.png"), original, 0664); err != nil {
  176. t.Fatalf("cannot write the binary file: %v", err)
  177. }
  178. if _, err := manager.Commit(repoPath, &CommitRequest{
  179. Message: "add icon",
  180. Files: []string{"icon.png"},
  181. Name: "Test User",
  182. Email: "test@arozos.local",
  183. }); err != nil {
  184. t.Fatalf("Commit() returned error: %v", err)
  185. }
  186. content, exists, err := manager.FileBlob(repoPath, "icon.png", "HEAD")
  187. if err != nil {
  188. t.Fatalf("FileBlob() returned error: %v", err)
  189. }
  190. if !exists {
  191. t.Fatalf("exists = false, want true")
  192. }
  193. if len(content) != len(original) {
  194. t.Fatalf("FileBlob() returned %d bytes, want %d", len(content), len(original))
  195. }
  196. for i := range original {
  197. if content[i] != original[i] {
  198. t.Fatalf("byte %d = %#x, want %#x", i, content[i], original[i])
  199. }
  200. }
  201. }
  202. // TestFileBlobFromSubfolderPath checks a path inside the repository resolves to
  203. // the same repository, matching every other Manager call.
  204. func TestFileBlobFromSubfolderPath(t *testing.T) {
  205. manager := newTestManager(t)
  206. repoPath := newTestRepo(t, manager)
  207. commitFile(t, manager, repoPath, "img/icon.png", "content", "add icon")
  208. nested := filepath.Join(repoPath, "img")
  209. content, exists, err := manager.FileBlob(nested, "img/icon.png", "HEAD")
  210. if err != nil {
  211. t.Fatalf("FileBlob() returned error: %v", err)
  212. }
  213. if !exists || string(content) != "content" {
  214. t.Errorf("FileBlob() = %q (exists %v), want the committed content", string(content), exists)
  215. }
  216. }