github.com/andrew00x/gomovies@v0.1.0/pkg/catalog/catalog_json_test.go (about) 1 package catalog 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/stretchr/testify/assert" 7 "log" 8 "os" 9 "path/filepath" 10 "text/template" 11 12 "testing" 13 14 "github.com/andrew00x/gomovies/pkg/api" 15 "github.com/andrew00x/gomovies/pkg/config" 16 ) 17 18 var conf config.Config 19 20 type devDrive struct { 21 name string 22 id string 23 label string 24 Dev string 25 Mount string 26 } 27 28 var testRoot string 29 30 const sda = "usb-WDC_WD64_00AAKS-00A7B0_00A1234567E7-0:0" 31 const sda1Label = "wd640" 32 const sdb = "usb-WDC_WD50_00AAKS-00A7B0_007E7123456A-0:0" 33 const sdb1Label = "wd500" 34 35 var drives []devDrive 36 var moviesDir string 37 var cartoonsDir string 38 var movies []api.Movie 39 40 func TestLoadCatalog(t *testing.T) { 41 setup() 42 43 index := indexMock{[]indexItem{}, []int{}} 44 indexFactory = func(_ *config.Config) (Index, error) { return &index, nil } 45 46 catalog, err := createJsonCatalog(&conf) 47 assert.Nil(t, err) 48 49 expected := []api.Movie{ 50 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true, DriveName: sda1Label}, 51 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true, DriveName: sda1Label}, 52 {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true, DriveName: sda1Label}, 53 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true, DriveName: sda1Label}, 54 } 55 catalogContent := catalog.All() 56 for i := range catalogContent { // ignore id 57 catalogContent[i].Id = 0 58 } 59 assert.ElementsMatch(t, expected, catalogContent) 60 61 expectedIndex := make([]indexItem, 0, len(expected)) 62 for _, i := range catalog.All() { 63 expectedIndex = append(expectedIndex, indexItem{i.Title, i.Id}) 64 } 65 assert.ElementsMatch(t, expectedIndex, index.added) 66 } 67 68 func TestCreateAndUpdateCatalog(t *testing.T) { 69 setup() 70 71 iceAge := filepath.Join(cartoonsDir, "ice age.avi") 72 mustCreateFile(iceAge) 73 mustSaveCatalogFile([]api.Movie{ 74 {File: iceAge, Title: filepath.Base(iceAge), DriveName: sdb1Label}, 75 }) 76 77 index := indexMock{[]indexItem{}, []int{}} 78 indexFactory = func(_ *config.Config) (Index, error) { return &index, nil } 79 80 catalog, err := createJsonCatalog(&conf) 81 assert.Nil(t, err) 82 83 expected := []api.Movie{ 84 {File: iceAge, Title: filepath.Base(iceAge), Available: true, DriveName: sdb1Label}, 85 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true, DriveName: sda1Label}, 86 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true, DriveName: sda1Label}, 87 {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true, DriveName: sda1Label}, 88 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true, DriveName: sda1Label}, 89 } 90 catalogContent := catalog.All() 91 for i := range catalogContent { // ignore id 92 catalogContent[i].Id = 0 93 } 94 assert.ElementsMatch(t, expected, catalogContent) 95 96 expectedIndex := make([]indexItem, 0, len(expected)) 97 for _, i := range catalog.All() { 98 expectedIndex = append(expectedIndex, indexItem{i.Title, i.Id}) 99 } 100 assert.ElementsMatch(t, expectedIndex, index.added) 101 } 102 103 func TestSaveCatalog(t *testing.T) { 104 setup() 105 106 movies := map[int]*api.Movie{ 107 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 108 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 109 3: {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 110 4: {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 111 } 112 catalog := &JsonCatalog{movies: movies} 113 err := catalog.Save() 114 assert.Nil(t, err) 115 116 f, err := os.Open(catalogFile) 117 assert.Nil(t, err) 118 119 parser := json.NewDecoder(f) 120 var saved map[int]*api.Movie 121 err = parser.Decode(&saved) 122 assert.Nil(t, err) 123 err = f.Close() 124 assert.Nil(t, err) 125 126 assert.Equal(t, movies, saved) 127 } 128 129 func TestGetById(t *testing.T) { 130 setup() 131 132 movies := map[int]*api.Movie{ 133 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 134 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 135 3: {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 136 4: {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 137 } 138 catalog := &JsonCatalog{movies: movies} 139 140 result, ok := catalog.Get(1) 141 142 assert.True(t, ok) 143 assert.Equal(t, *movies[1], result) 144 } 145 146 func TestGetByIdReturnsEmptyResultWhenFileDoesNotExist(t *testing.T) { 147 setup() 148 movies := make(map[int]*api.Movie) 149 catalog := &JsonCatalog{movies: movies} 150 151 result, ok := catalog.Get(1) 152 153 assert.False(t, ok) 154 assert.Equal(t, api.Movie{}, result) 155 } 156 157 func TestFindByNameInCatalog(t *testing.T) { 158 setup() 159 160 movies := map[int]*api.Movie{ 161 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 162 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 163 3: {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 164 4: {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 165 } 166 167 index := indexMock{added: []indexItem{}, found: []int{1, 3}} 168 catalog := &JsonCatalog{movies: movies, index: &index} 169 170 result := catalog.Find("whatever we have in index") 171 172 expected := []api.Movie{*movies[1], *movies[3]} 173 assert.Equal(t, expected, result) 174 } 175 176 func TestRemoveNonexistentFilesFromCatalog(t *testing.T) { 177 setup() 178 iceAge := filepath.Join(cartoonsDir, "ice age.avi") 179 mustRemoveFiles(iceAge) 180 mustSaveCatalogFile([]api.Movie{ 181 {File: iceAge, Title: filepath.Base(iceAge), DriveName: sdb1Label}, 182 }) 183 184 index := indexMock{[]indexItem{}, []int{}} 185 indexFactory = func(_ *config.Config) (Index, error) { return &index, nil } 186 187 catalog, err := createJsonCatalog(&conf) 188 assert.Nil(t, err) 189 190 expected := []api.Movie{ 191 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true, DriveName: sda1Label}, 192 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true, DriveName: sda1Label}, 193 {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true, DriveName: sda1Label}, 194 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true, DriveName: sda1Label}, 195 } 196 catalogContent := catalog.All() 197 for i := range catalogContent { // ignore id 198 catalogContent[i].Id = 0 199 } 200 assert.ElementsMatch(t, expected, catalogContent) 201 202 expectedIndex := make([]indexItem, 0, len(expected)) 203 for _, i := range catalog.All() { 204 expectedIndex = append(expectedIndex, indexItem{i.Title, i.Id}) 205 } 206 assert.ElementsMatch(t, expectedIndex, index.added) 207 } 208 209 func TestKeepNonexistentFilesWhenCorrespondedDriveIsUnmounted(t *testing.T) { 210 setup() 211 otherMoviesDir := filepath.Join(testRoot, "media", "pi", "unmount", "movies") 212 mustSaveCatalogFile([]api.Movie{ 213 {Id: 1, File: filepath.Join(otherMoviesDir, "rush hour 1.avi"), Title: "rush hour 1.avi", DriveName: "unmount"}, 214 {Id: 2, File: filepath.Join(otherMoviesDir, "rush hour 2.mkv"), Title: "rush hour 2.mkv", DriveName: "unmount"}, 215 }) 216 217 index := indexMock{[]indexItem{}, []int{}} 218 indexFactory = func(_ *config.Config) (Index, error) { return &index, nil } 219 220 conf := config.Config{VideoFileExts: []string{".mkv", ".avi"}, Dirs: []string{moviesDir, cartoonsDir, otherMoviesDir}} 221 catalog, err := createJsonCatalog(&conf) 222 assert.Nil(t, err) 223 224 expected := []api.Movie{ 225 {File: filepath.Join(otherMoviesDir, "rush hour 1.avi"), Title: "rush hour 1.avi", DriveName: "unmount", Available: false}, 226 {File: filepath.Join(otherMoviesDir, "rush hour 2.mkv"), Title: "rush hour 2.mkv", DriveName: "unmount", Available: false}, 227 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", DriveName: sda1Label, Available: true}, 228 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", DriveName: sda1Label, Available: true}, 229 {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", DriveName: sda1Label, Available: true}, 230 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", DriveName: sda1Label, Available: true}, 231 } 232 catalogContent := catalog.All() 233 for i := range catalogContent { // ignore id 234 catalogContent[i].Id = 0 235 } 236 assert.ElementsMatch(t, expected, catalogContent) 237 238 expectedIndex := make([]indexItem, 0, len(expected)) 239 for _, i := range catalog.All() { 240 expectedIndex = append(expectedIndex, indexItem{i.Title, i.Id}) 241 } 242 assert.ElementsMatch(t, expectedIndex, index.added) 243 } 244 245 func TestRefreshCatalog(t *testing.T) { 246 setup() 247 index := indexMock{[]indexItem{}, []int{}} 248 indexFactory = func(_ *config.Config) (Index, error) { return &index, nil } 249 250 conf := config.Config{VideoFileExts: []string{".mkv", ".avi"}, Dirs: []string{moviesDir}} 251 catalog, err := createJsonCatalog(&conf) 252 assert.Nil(t, err) 253 254 for _, p := range []string{ 255 filepath.Join(moviesDir, "rush hour 1.avi"), 256 filepath.Join(moviesDir, "rush hour 2.mkv")} { 257 mustCreateFile(p) 258 } 259 mustRemoveMovieFiles([]api.Movie{movies[2]}) 260 261 index = indexMock{[]indexItem{}, []int{}} 262 err = catalog.Refresh() 263 assert.Nil(t, err) 264 265 expected := []api.Movie{ 266 {File: filepath.Join(moviesDir, "rush hour 1.avi"), Title: "rush hour 1.avi", DriveName: sda1Label, Available: true}, 267 {File: filepath.Join(moviesDir, "rush hour 2.mkv"), Title: "rush hour 2.mkv", DriveName: sda1Label, Available: true}, 268 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", DriveName: sda1Label, Available: true}, 269 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", DriveName: sda1Label, Available: true}, 270 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", DriveName: sda1Label, Available: true}, 271 } 272 273 catalogContent := catalog.All() 274 for i := range catalogContent { // ignore id 275 catalogContent[i].Id = 0 276 } 277 assert.ElementsMatch(t, expected, catalogContent) 278 279 expectedIndex := make([]indexItem, 0, len(expected)) 280 for _, i := range catalog.All() { 281 expectedIndex = append(expectedIndex, indexItem{i.Title, i.Id}) 282 } 283 assert.ElementsMatch(t, expectedIndex, index.added) 284 } 285 286 func TestUpdateCatalog(t *testing.T) { 287 setup() 288 289 movies := map[int]*api.Movie{ 290 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 291 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 292 3: {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 293 4: {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 294 } 295 catalog := &JsonCatalog{movies: movies} 296 297 updated, err := catalog.Update(api.Movie{Id: 1, TMDbId: 101}) 298 assert.Nil(t, err) 299 assert.Equal(t, 101, movies[1].TMDbId) 300 assert.Equal(t, 101, updated.TMDbId) 301 302 f, err := os.Open(catalogFile) 303 assert.Nil(t, err) 304 parser := json.NewDecoder(f) 305 var saved map[int]*api.Movie 306 err = parser.Decode(&saved) 307 assert.Nil(t, err) 308 err = f.Close() 309 assert.Nil(t, err) 310 assert.Equal(t, movies, saved) 311 } 312 313 func TestUpdateCatalogFailsWhenUpdatedFileDoesNotExist(t *testing.T) { 314 movies := make(map[int]*api.Movie) 315 catalog := &JsonCatalog{movies: movies} 316 317 _, err := catalog.Update(api.Movie{Id: 1}) 318 assert.NotNil(t, err) 319 assert.Equal(t, "unknown movie, id: 1, title: ", err.Error()) 320 } 321 322 func TestAddTag(t *testing.T) { 323 movies := map[int]*api.Movie{ 324 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 325 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 326 3: {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 327 4: {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 328 } 329 index := indexMock{[]indexItem{}, []int{}} 330 catalog := &JsonCatalog{movies: movies, index: &index} 331 err := catalog.AddTag("collection one", 2) 332 assert.Nil(t, err) 333 var tagged []int 334 for i := range index.added { 335 if index.added[i].tag == "collection one" { 336 tagged = append(tagged, index.added[i].id) 337 } 338 } 339 assert.Equal(t, []int{2}, tagged) 340 } 341 342 func TestAddTagFailsWhenTryTagNotExistedMovie(t *testing.T) { 343 movies := map[int]*api.Movie{ 344 1: {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 345 2: {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 346 } 347 index := indexMock{[]indexItem{}, []int{}} 348 catalog := &JsonCatalog{movies: movies, index: &index} 349 err := catalog.AddTag("collection one", 3) 350 assert.NotNil(t, err) 351 assert.Equal(t, "unable add tag for unknown movie, id: 3", err.Error()) 352 } 353 354 func mustCreateEtcDir(rootDir string, drives []devDrive) (etc string) { 355 var wd string 356 var err error 357 if wd, err = os.Getwd(); err != nil { 358 log.Fatal(err) 359 } 360 var tpl *template.Template 361 if tpl, err = template.New("mtab").ParseFiles(filepath.Join(wd, "testdata", "mtab")); err != nil { 362 log.Fatal(err) 363 } 364 etc = filepath.Join(rootDir, "etc") 365 mustCreateDir(etc) 366 var mtabFile *os.File 367 if mtabFile, err = os.OpenFile(filepath.Join(etc, "mtab"), os.O_RDWR|os.O_CREATE, 0644); err != nil { 368 log.Fatal(err) 369 } 370 var mounts []devDrive 371 for _, d := range drives { 372 if d.Mount != "" { 373 mounts = append(mounts, d) 374 } 375 } 376 if err = tpl.Execute(mtabFile, mounts); err != nil { 377 log.Fatal(err) 378 } 379 if err = mtabFile.Close(); err != nil { 380 log.Fatal(err) 381 } 382 return 383 } 384 385 func setup() { 386 tmp := os.Getenv("TMPDIR") 387 testRoot = filepath.Join(tmp, "CatalogTest") 388 389 if err := os.RemoveAll(testRoot); err != nil && !os.IsNotExist(err) { 390 log.Fatal(err) 391 } 392 drives = []devDrive{ 393 {name: "mmcblk0p0", id: "mmc-SL16G_0x2a1994a5"}, 394 {name: "mmcblk0p1", id: "mmc-SL16G_0x2a1994a5-part1", label: "RECOVERY", Dev: "/dev/mmcblk0p1"}, 395 {name: "mmcblk0p2", id: "mmc-SL16G_0x2a1994a5-part2", Dev: "/dev/mmcblk0p2"}, 396 {name: "mmcblk0p5", id: "mmc-SL16G_0x2a1994a5-part5", label: "SETTINGS", Dev: "/dev/mmcblk0p5"}, 397 {name: "mmcblk0p6", id: "mmc-SL16G_0x2a1994a5-part6", label: "boot", Dev: "/dev/mmcblk0p6"}, 398 {name: "mmcblk0p7", id: "mmc-SL16G_0x2a1994a5-part7", label: "root", Dev: "/dev/mmcblk0p7"}, 399 {name: "mmcblk0p8", id: "mmc-SL16G_0x2a1994a5-part8", label: "data", Dev: "/dev/mmcblk0p8"}, 400 {name: "sda", id: sda, Dev: fmt.Sprintf("/dev/%s", sda)}, 401 {name: "sda1", id: sda1Label, Mount: fmt.Sprintf("%s/media/pi/%s", testRoot, sda1Label), Dev: "/dev/sda1"}, 402 {name: "sdb", id: sdb, Dev: fmt.Sprintf("/dev/%s", sdb)}, 403 {name: "sdb1", id: sdb1Label, Mount: fmt.Sprintf("%s/media/pi/%s", testRoot, sdb1Label), Dev: "/dev/sdb1"}} 404 devcd = mustCreateDevDir(testRoot, drives) 405 etcDir = mustCreateEtcDir(testRoot, drives) 406 moviesDir = filepath.Join(testRoot, "media", "pi", sda1Label, "movies") 407 mustCreateDir(moviesDir) 408 cartoonsDir = filepath.Join(testRoot, "media", "pi", sdb1Label, "cartoons") 409 mustCreateDir(cartoonsDir) 410 movies = []api.Movie{ 411 {File: filepath.Join(moviesDir, "star wars", "star wars 1.avi"), Title: "star wars 1.avi", Available: true}, 412 {File: filepath.Join(moviesDir, "star wars", "star wars 2.mkv"), Title: "star wars 2.mkv", Available: true}, 413 {File: filepath.Join(moviesDir, "gladiator.mkv"), Title: "gladiator.mkv", Available: true}, 414 {File: filepath.Join(moviesDir, "green mile.mkv"), Title: "green mile.mkv", Available: true}, 415 } 416 mustCreateMovieFiles(movies) 417 confDir := filepath.Join(testRoot, "gomovies", "config") 418 mustCreateDir(confDir) 419 catalogFile = filepath.Join(confDir, "catalog.json") 420 conf = config.Config{VideoFileExts: []string{".mkv", ".avi"}, Dirs: []string{moviesDir, cartoonsDir}} 421 } 422 423 func mustCreateDevDir(rootDir string, drives []devDrive) (devDir string) { 424 devDir = filepath.Join(rootDir, "dev") 425 disk := filepath.Join(devDir, "disk") 426 byId := filepath.Join(disk, "by-id") 427 byLabel := filepath.Join(disk, "by-label") 428 429 mustCreateDir(devDir) 430 mustCreateDir(disk) 431 mustCreateDir(byId) 432 mustCreateDir(byLabel) 433 434 var err error 435 var wd string 436 if wd, err = os.Getwd(); err != nil { 437 log.Fatal(err) 438 } 439 for _, d := range drives { 440 mustCreateFile(filepath.Join(devDir, d.name)) 441 if err = os.Chdir(byId); err != nil { 442 log.Fatal(err) 443 } 444 if err = os.Symlink(filepath.Join("../..", d.name), filepath.Join(byId, d.id)); err != nil { 445 log.Fatal(err) 446 } 447 if d.label != "" { 448 if err = os.Chdir(byId); err != nil { 449 log.Fatal(err) 450 } 451 if err = os.Symlink(filepath.Join("../..", d.name), filepath.Join(byLabel, d.label)); err != nil { 452 log.Fatal(err) 453 } 454 } 455 } 456 if err = os.Chdir(wd); err != nil { 457 log.Fatal(err) 458 } 459 return 460 } 461 462 func mustCreateMovieFiles(movies []api.Movie) { 463 for _, m := range movies { 464 mustCreateDir(filepath.Dir(m.File)) 465 mustCreateFile(m.File) 466 } 467 } 468 469 func mustRemoveMovieFiles(movies []api.Movie) { 470 var files []string 471 for _, m := range movies { 472 files = append(files, m.File) 473 } 474 mustRemoveFiles(files...) 475 } 476 477 func mustCreateDir(dir string) { 478 err := os.MkdirAll(dir, 0777) 479 if err != nil { 480 log.Fatal(err) 481 } 482 } 483 484 func mustCreateFile(path string) { 485 var f *os.File 486 var err error 487 if f, err = os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0644); err != nil { 488 log.Fatal(err) 489 } 490 if err = f.Close(); err != nil { 491 log.Fatal(err) 492 } 493 } 494 495 func mustRemoveFiles(paths ...string) { 496 for _, path := range paths { 497 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 498 log.Fatal(err) 499 } 500 } 501 } 502 503 func mustSaveCatalogFile(movies []api.Movie) { 504 var err error 505 var file *os.File 506 if file, err = os.OpenFile(catalogFile, os.O_WRONLY|os.O_CREATE, 0644); err != nil { 507 log.Fatal(err) 508 } 509 m := make(map[int]api.Movie, len(movies)) 510 for _, f := range movies { 511 m[f.Id] = f 512 } 513 encoder := json.NewEncoder(file) 514 if err = encoder.Encode(m); err != nil { 515 log.Fatal(err) 516 } 517 if err = file.Close(); err != nil { 518 log.Fatal(err) 519 } 520 } 521 522 type indexItem struct { 523 tag string 524 id int 525 } 526 527 type indexMock struct { 528 added []indexItem 529 found []int 530 } 531 532 func (idx *indexMock) Add(title string, id int) { 533 idx.added = append(idx.added, indexItem{title, id}) 534 } 535 536 func (idx *indexMock) Find(_ string) []int { 537 return idx.found 538 }