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  }