package main_test import ( "context" "io/ioutil" "os" "path" "slices" "testing" main "gitea/show-ingestion" ) func TestRunChoosesOne(t *testing.T) { ind := t.TempDir() outd := t.TempDir() for _, given := range []string{ "Survivor.AU.S12E11.1080p.HEVC.x265-MeGusta[EZTVx.to].mkv", "Survivor.AU.S12E11.720p.HEVC.x265-MeGusta[EZTVx.to].mkv", "Survivor.AU.S12E12.720p.HEVC.x265-MeGusta[EZTVx.to].mkv", } { ioutil.WriteFile(path.Join(ind, given), []byte{}, os.ModePerm) } want := map[string]bool{ "Australian_Survivor_S12E11.mkv": false, "Australian_Survivor_S12E12.mkv": false, } if err := main.RunWith(context.Background(), outd, ind, []string{ ".urvivor.[Aa][Uu].*[sS](?P[0-9]+)[eE](?P[0-9]*).*1080.*MeGusta", ".urvivor.[Aa][Uu].*[sS](?P[0-9]+)[eE](?P[0-9]*).*720.*MeGusta", }, main.Fields{ Title: "Australian_Survivor", }, func(outf, inf string) error { want[path.Base(outf)] = true return nil }, ); err != nil { t.Fatal(err) } for k, v := range want { if !v { t.Errorf("did not mv_n_ln(outf=%s)", k) } } } func TestRunWith(t *testing.T) { cases := map[string]struct { given []string patterns []string overrides main.Fields want []string }{ "empty": {}, "fallback survivor": { given: []string{ "Survivor.AU.S12E11.1080p.HEVC.x265-MeGusta[EZTVx.to].mkv", "Survivor.AU.S12E11.720p.HEVC.x265-MeGusta[EZTVx.to].mkv", "Survivor.AU.S12E12.720p.HEVC.x265-MeGusta[EZTVx.to].mkv", }, patterns: []string{ ".urvivor.[Aa][Uu].*[sS](?P[0-9]+)[eE](?P[0-9]*).*1080.*MeGusta", ".urvivor.[Aa][Uu].*[sS](?P[0-9]+)[eE](?P[0-9]*).*720.*MeGusta", }, overrides: main.Fields{ Title: "Australian_Survivor", }, want: []string{ "Australian_Survivor_S12E11.mkv", "Australian_Survivor_S12E12.mkv", }, }, "easy w group": { given: []string{ "[SubsPlease] Tokidoki Bosotto Russia-go de Dereru Tonari no Alya-san - 01 (720p) [A12844D5].mkv", "[SubsPlease] Tokidoki Bosotto Russia-go de Dereru Tonari no Alya-san - 02 (720p) [2608F490].mkv", }, patterns: []string{ `^\[[^\]]*\] (?P.*) - (?<episode>[0-9]*)`, }, overrides: main.Fields{ Season: "01", }, want: []string{ "Tokidoki_Bosotto_Russia-go_de_Dereru_Tonari_no_Alya-san_S01E01.mkv", "Tokidoki_Bosotto_Russia-go_de_Dereru_Tonari_no_Alya-san_S01E02.mkv", }, }, } for name, d := range cases { c := d t.Run(name, func(t *testing.T) { ind := t.TempDir() for _, f := range c.given { ioutil.WriteFile(path.Join(ind, f), []byte{}, os.ModePerm) } outd := t.TempDir() if err := main.RunWith(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { t.Fatal("err on first run:", err) } else if err := main.RunWith(context.Background(), outd, ind, c.patterns, c.overrides, main.RealMvNLn); err != nil { t.Fatal("err on second run:", err) } for _, f := range c.want { if stat, err := os.Stat(path.Join(outd, f)); os.IsNotExist(err) { t.Errorf("expected %s", f) } else if !stat.Mode().IsRegular() { t.Errorf("%s not a regular file: %v", f, stat.Mode()) } } if entries, err := os.ReadDir(outd); err != nil { t.Error("failed to list outdir: %w", err) } else { for _, entry := range entries { t.Logf("%s", entry.Name()) if !slices.Contains(c.want, path.Base(entry.Name())) { t.Errorf("unexpected %s", entry.Name()) } if !entry.Type().IsRegular() { t.Errorf("non-regular file %s in out: %v", entry.Name(), entry.Type()) } } } if entries, err := os.ReadDir(ind); err != nil { t.Error("failed to list indir: %w", err) } else { for _, entry := range entries { inf := path.Join(ind, entry.Name()) if _, err := os.Stat(inf); err != nil { t.Errorf("%s no longer in ind: %v", inf, err) } } } }) } } func TestRecursive(t *testing.T) { was, _ := os.Getwd() t.Cleanup(func() { os.Chdir(was) }) os.Chdir(t.TempDir()) outd := t.TempDir() os.MkdirAll(path.Join(outd, "A"), os.ModePerm) // use config write("./showA/.show-ingestion.yaml", `{ "c": { "title": "A", "season": "A", "episode": "A" }, "p": [".*"], "o": "`+outd+`/A" }`) write("./showA/file.a") // parse files and const wins write("./showB/.show-ingestion.yaml", `{ "o": "`+outd+`/B_{{.Title}}_{{.Season}}_{{.Episode}}", "p": [], "c": {"title": "TITLE"} }`) write("./showB/title S01E02.b") // use file pattern write("./dirA/showC/.show-ingestion.yaml", `{ "o": "`+outd+`/C", "p": ["^(?P<title>.) (?P<season>.) (?P<episode>.)"] }`) write("./dirA/showC/t s e.c") // dry run write("./dirA/showD/.show-ingestion.yaml", `{ "o": "`+outd+`/D", "d": true }`) write("./dirA/showD/title S02E04.d") // not configured os.MkdirAll("./dirB/showE", os.ModePerm) write("./dirB/showE/title S03E06.e") if err := main.Recursive(context.Background()); err != nil { t.Fatal(err) } exists(t, path.Join(outd, "A", "A_SAEA.a")) exists(t, path.Join(outd, "B_TITLE_01_02", "TITLE_S01E02.b")) exists(t, path.Join(outd, "C", "t_SsEe.c")) notExists(t, path.Join(outd, "D", "title_S02E04.d")) notExists(t, path.Join(outd, "title_S03E06.e")) } func write(f string, b ...string) { if len(b) == 0 { b = append(b, "") } os.MkdirAll(path.Dir(f), os.ModePerm) os.WriteFile(f, []byte(b[0]), os.ModePerm) } func exists(t *testing.T, p string) { if _, err := os.Stat(p); os.IsNotExist(err) { d := path.Dir(path.Dir(p)) t.Errorf("expected %s of %s (%+v)", path.Base(p), d, ls(d)) } } func notExists(t *testing.T, p string) { if _, err := os.Stat(p); !os.IsNotExist(err) { d := path.Dir(path.Dir(p)) t.Errorf("unexpected %s of %s (%+v)", path.Base(p), d, ls(d)) } } func ls(d string) []string { result := []string{} entries, _ := os.ReadDir(d) for _, entry := range entries { p := path.Join(d, entry.Name()) if entry.IsDir() { result = append(result, ls(p)...) } else { result = append(result, p) } } slices.Sort(result) return result } func TestParse(t *testing.T) { cases := map[string]struct { pattern string want main.Fields }{ "[SubsPlease] Tokidoki Bosotto Russia-go de Dereru Tonari no Alya-san - 01 (720p) [A12844D5].mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "Tokidoki Bosotto Russia-go de Dereru Tonari no Alya-san", Season: "", Episode: "01", }, }, "Survivor.AU.S12E11.1080p.HEVC.x265-MeGusta[EZTVx.to].mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "Survivor.AU", Season: "12", Episode: "11", }, }, "DAN DA DAN (2024) S01E01v2 (1080p WEB-DL H264 AAC DDP 2.0 Dual-Audio) [MALD].mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "DAN DA DAN (2024)", Season: "01", Episode: "01", }, }, "ZENSHU.S01E01.1080p.AMZN.WEB-DL.MULTi.DDP2.0.H.264.MSubs-ToonsHub.mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "ZENSHU", Season: "01", Episode: "01", }, }, "[Yameii] My Hero Academia - S07E08 [English Dub] [CR WEB-DL 720p] [DE5FFC3E].mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "My Hero Academia", Season: "07", Episode: "08", }, }, "Ranma1-2.2024.S01E03.Because.Theres.Someone.He.Likes.1080p.NF.WEB-DL.AAC2.0.H.264-VARYG.mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "Ranma1-2.2024", Season: "01", Episode: "03", }, }, "[Yameii] The Apothecary Diaries - S02E03 [English Dub] [CR WEB-DL 720p] [FD3E7434].mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "The Apothecary Diaries", Season: "02", Episode: "03", }, }, "The.Dinner.Table.Detective.S01E01.Welcome.to.the.murderous.party.File.1.1080p.AMZN.WEB-DL.DDP2.0.H.264-VARYG.mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "The.Dinner.Table.Detective", Season: "01", Episode: "01", }, }, "[Reza] Wistoria Wand and Sword - S01E01.mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "Wistoria Wand and Sword", Season: "01", Episode: "01", }, }, "[EMBER] Ao no Hako - 01.mkv": { pattern: main.PatternGroupTitleHyphenSE, want: main.Fields{ Title: "Ao no Hako", Season: "", Episode: "01", }, }, } for f, d := range cases { c := d t.Run(f, func(t *testing.T) { got, _ := main.Parse(f, c.pattern) if got != c.want { t.Errorf("expected \n\t%+v but got \n\t%+v", c.want, got) } }) } }