name_test.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package model
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. const (
  7. part80 = "88888888888888888888888888888888888888888888888888888888888888888888888888888888"
  8. part350 = "33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333"
  9. )
  10. func TestParseNameParts(t *testing.T) {
  11. cases := []struct {
  12. in string
  13. want Name
  14. wantValidDigest bool
  15. }{
  16. {
  17. in: "host/namespace/model:tag",
  18. want: Name{
  19. Host: "host",
  20. Namespace: "namespace",
  21. Model: "model",
  22. Tag: "tag",
  23. },
  24. },
  25. {
  26. in: "host/namespace/model",
  27. want: Name{
  28. Host: "host",
  29. Namespace: "namespace",
  30. Model: "model",
  31. },
  32. },
  33. {
  34. in: "namespace/model",
  35. want: Name{
  36. Namespace: "namespace",
  37. Model: "model",
  38. },
  39. },
  40. {
  41. in: "model",
  42. want: Name{
  43. Model: "model",
  44. },
  45. },
  46. {
  47. in: "h/nn/mm:t",
  48. want: Name{
  49. Host: "h",
  50. Namespace: "nn",
  51. Model: "mm",
  52. Tag: "t",
  53. },
  54. },
  55. {
  56. in: part80 + "/" + part80 + "/" + part80 + ":" + part80,
  57. want: Name{
  58. Host: part80,
  59. Namespace: part80,
  60. Model: part80,
  61. Tag: part80,
  62. },
  63. },
  64. {
  65. in: part350 + "/" + part80 + "/" + part80 + ":" + part80,
  66. want: Name{
  67. Host: part350,
  68. Namespace: part80,
  69. Model: part80,
  70. Tag: part80,
  71. },
  72. },
  73. {
  74. in: "@digest",
  75. want: Name{
  76. RawDigest: "digest",
  77. },
  78. wantValidDigest: false,
  79. },
  80. {
  81. in: "model@sha256:123",
  82. want: Name{
  83. Model: "model",
  84. RawDigest: "sha256:123",
  85. },
  86. wantValidDigest: true,
  87. },
  88. }
  89. for _, tt := range cases {
  90. t.Run(tt.in, func(t *testing.T) {
  91. got := ParseNameBare(tt.in)
  92. if !reflect.DeepEqual(got, tt.want) {
  93. t.Errorf("parseName(%q) = %v; want %v", tt.in, got, tt.want)
  94. }
  95. })
  96. }
  97. }
  98. var testCases = map[string]bool{ // name -> valid
  99. "host/namespace/model:tag": true,
  100. "host/namespace/model": false,
  101. "namespace/model": false,
  102. "model": false,
  103. "@sha256-1000000000000000000000000000000000000000000000000000000000000000": false,
  104. "model@sha256-1000000000000000000000000000000000000000000000000000000000000000": false,
  105. "model@sha256:1000000000000000000000000000000000000000000000000000000000000000": false,
  106. // long (but valid)
  107. part80 + "/" + part80 + "/" + part80 + ":" + part80: true,
  108. part350 + "/" + part80 + "/" + part80 + ":" + part80: true,
  109. "h/nn/mm:t@sha256-1000000000000000000000000000000000000000000000000000000000000000": true, // bare minimum part sizes
  110. "h/nn/mm:t@sha256:1000000000000000000000000000000000000000000000000000000000000000": true, // bare minimum part sizes
  111. "m": false, // model too short
  112. "n/mm:": false, // namespace too short
  113. "h/n/mm:t": false, // namespace too short
  114. "@t": false, // digest too short
  115. "mm@d": false, // digest too short
  116. // invalids
  117. "^": false,
  118. "mm:": false,
  119. "/nn/mm": false,
  120. "//": false,
  121. "//mm": false,
  122. "hh//": false,
  123. "//mm:@": false,
  124. "00@": false,
  125. "@": false,
  126. // not starting with alphanum
  127. "-hh/nn/mm:tt@dd": false,
  128. "hh/-nn/mm:tt@dd": false,
  129. "hh/nn/-mm:tt@dd": false,
  130. "hh/nn/mm:-tt@dd": false,
  131. "hh/nn/mm:tt@-dd": false,
  132. "": false,
  133. // hosts
  134. "host:https/namespace/model:tag": true,
  135. // colon in non-host part before tag
  136. "host/name:space/model:tag": false,
  137. }
  138. func TestNameparseNameDefault(t *testing.T) {
  139. const name = "xx"
  140. n := ParseName(name)
  141. got := n.String()
  142. want := "registry.ollama.ai/library/xx:latest"
  143. if got != want {
  144. t.Errorf("parseName(%q).String() = %q; want %q", name, got, want)
  145. }
  146. }
  147. func TestNameIsValid(t *testing.T) {
  148. var numStringTests int
  149. for s, want := range testCases {
  150. n := ParseNameBare(s)
  151. t.Logf("n: %#v", n)
  152. got := n.IsValid()
  153. if got != want {
  154. t.Errorf("parseName(%q).IsValid() = %v; want %v", s, got, want)
  155. }
  156. // Test roundtrip with String
  157. if got {
  158. got := ParseNameBare(s).String()
  159. if got != s {
  160. t.Errorf("parseName(%q).String() = %q; want %q", s, got, s)
  161. }
  162. numStringTests++
  163. }
  164. }
  165. if numStringTests == 0 {
  166. t.Errorf("no tests for Name.String")
  167. }
  168. }
  169. func TestNameIsValidPart(t *testing.T) {
  170. cases := []struct {
  171. kind partKind
  172. s string
  173. want bool
  174. }{
  175. {kind: kindHost, s: "", want: false},
  176. {kind: kindHost, s: "a", want: true},
  177. {kind: kindHost, s: "a.", want: true},
  178. {kind: kindHost, s: "a.b", want: true},
  179. {kind: kindHost, s: "a:123", want: true},
  180. {kind: kindHost, s: "a:123/aa/bb", want: false},
  181. {kind: kindNamespace, s: "bb", want: true},
  182. {kind: kindNamespace, s: "a.", want: false},
  183. {kind: kindModel, s: "-h", want: false},
  184. {kind: kindDigest, s: "sha256-1000000000000000000000000000000000000000000000000000000000000000", want: true},
  185. }
  186. for _, tt := range cases {
  187. t.Run(tt.s, func(t *testing.T) {
  188. got := isValidPart(tt.kind, tt.s)
  189. if got != tt.want {
  190. t.Errorf("isValidPart(%s, %q) = %v; want %v", tt.kind, tt.s, got, tt.want)
  191. }
  192. })
  193. }
  194. }
  195. func FuzzName(f *testing.F) {
  196. for s := range testCases {
  197. f.Add(s)
  198. }
  199. f.Fuzz(func(t *testing.T, s string) {
  200. n := ParseNameBare(s)
  201. if n.IsValid() {
  202. parts := [...]string{n.Host, n.Namespace, n.Model, n.Tag, n.RawDigest}
  203. for _, part := range parts {
  204. if part == ".." {
  205. t.Errorf("unexpected .. as valid part")
  206. }
  207. if len(part) > 350 {
  208. t.Errorf("part too long: %q", part)
  209. }
  210. }
  211. if n.String() != s {
  212. t.Errorf("String() = %q; want %q", n.String(), s)
  213. }
  214. }
  215. })
  216. }