feat: add tests

This commit is contained in:
CDN 2025-04-23 16:30:45 +08:00
parent 44c7e9bee5
commit bb87f058f0
Signed by: CDN
GPG key ID: 0C656827F9F80080
17 changed files with 4436 additions and 80 deletions

View file

@ -0,0 +1,518 @@
package lrc
import (
"os"
"path/filepath"
"strings"
"testing"
"sub-cli/internal/model"
)
func TestParse(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[al:Test Album]
[by:Test Creator]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
[00:09.50]This is the third line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Test parsing
lyrics, err := Parse(testFile)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Verify results
if len(lyrics.Timeline) != 3 {
t.Errorf("Expected 3 timeline entries, got %d", len(lyrics.Timeline))
}
if len(lyrics.Content) != 3 {
t.Errorf("Expected 3 content entries, got %d", len(lyrics.Content))
}
// Check metadata
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
if lyrics.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected artist 'Test Artist', got '%s'", lyrics.Metadata["ar"])
}
if lyrics.Metadata["al"] != "Test Album" {
t.Errorf("Expected album 'Test Album', got '%s'", lyrics.Metadata["al"])
}
if lyrics.Metadata["by"] != "Test Creator" {
t.Errorf("Expected creator 'Test Creator', got '%s'", lyrics.Metadata["by"])
}
// Check first timeline entry
if lyrics.Timeline[0].Hours != 0 || lyrics.Timeline[0].Minutes != 0 ||
lyrics.Timeline[0].Seconds != 1 || lyrics.Timeline[0].Milliseconds != 0 {
t.Errorf("First entry time: expected 00:01.00, got %+v", lyrics.Timeline[0])
}
// Check third timeline entry
if lyrics.Timeline[2].Hours != 0 || lyrics.Timeline[2].Minutes != 0 ||
lyrics.Timeline[2].Seconds != 9 || lyrics.Timeline[2].Milliseconds != 500 {
t.Errorf("Third entry time: expected 00:09.50, got %+v", lyrics.Timeline[2])
}
// Check content
if lyrics.Content[0] != "This is the first line." {
t.Errorf("First entry content: expected 'This is the first line.', got '%s'", lyrics.Content[0])
}
}
func TestGenerate(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
"ar": "Test Artist",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0},
},
Content: []string{
"This is the first line.",
"This is the second line.",
},
}
// Generate LRC file
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := Generate(lyrics, outputFile)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
// Verify generated content
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
// Check content
lines := strings.Split(string(content), "\n")
if len(lines) < 4 {
t.Fatalf("Expected at least 4 lines, got %d", len(lines))
}
hasTitleLine := false
hasFirstTimeline := false
for _, line := range lines {
if line == "[ti:Test LRC File]" {
hasTitleLine = true
}
if line == "[00:01.000]This is the first line." {
hasFirstTimeline = true
}
}
if !hasTitleLine {
t.Errorf("Expected title line '[ti:Test LRC File]' not found")
}
if !hasFirstTimeline {
t.Errorf("Expected timeline line '[00:01.000]This is the first line.' not found")
}
}
func TestConvertToSubtitle(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Convert to subtitle
subtitle, err := ConvertToSubtitle(testFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed: %v", err)
}
// Check result
if subtitle.Format != "lrc" {
t.Errorf("Expected format 'lrc', got '%s'", subtitle.Format)
}
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
if len(subtitle.Entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(subtitle.Entries))
}
// Check first entry
if subtitle.Entries[0].Text != "This is the first line." {
t.Errorf("Expected first entry text 'This is the first line.', got '%s'", subtitle.Entries[0].Text)
}
// Check metadata
if subtitle.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected metadata 'ar' to be 'Test Artist', got '%s'", subtitle.Metadata["ar"])
}
}
func TestConvertFromSubtitle(t *testing.T) {
// Create test subtitle
subtitle := model.NewSubtitle()
subtitle.Format = "lrc"
subtitle.Title = "Test LRC File"
subtitle.Metadata["ar"] = "Test Artist"
entry1 := model.NewSubtitleEntry()
entry1.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0}
entry1.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 4, Milliseconds: 0}
entry1.Text = "This is the first line."
entry2 := model.NewSubtitleEntry()
entry2.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0}
entry2.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 8, Milliseconds: 0}
entry2.Text = "This is the second line."
subtitle.Entries = append(subtitle.Entries, entry1, entry2)
// Convert from subtitle to LRC
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := ConvertFromSubtitle(subtitle, outputFile)
if err != nil {
t.Fatalf("ConvertFromSubtitle failed: %v", err)
}
// Verify by parsing back
lyrics, err := Parse(outputFile)
if err != nil {
t.Fatalf("Failed to parse output file: %v", err)
}
if len(lyrics.Timeline) != 2 {
t.Errorf("Expected 2 entries, got %d", len(lyrics.Timeline))
}
if lyrics.Content[0] != "This is the first line." {
t.Errorf("Expected first entry content 'This is the first line.', got '%s'", lyrics.Content[0])
}
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title metadata 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
}
func TestFormat(t *testing.T) {
// Create test LRC file with inconsistent timestamp formatting
content := `[ti:Test LRC File]
[ar:Test Artist]
[0:1.0]This is the first line.
[0:5]This is the second line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Format the file
err := Format(testFile)
if err != nil {
t.Fatalf("Format failed: %v", err)
}
// Verify by parsing back
lyrics, err := Parse(testFile)
if err != nil {
t.Fatalf("Failed to parse formatted file: %v", err)
}
// Check that timestamps are formatted correctly
if lyrics.Timeline[0].Seconds != 1 || lyrics.Timeline[0].Milliseconds != 0 {
t.Errorf("Expected first timestamp to be 00:01.000, got %+v", lyrics.Timeline[0])
}
// Verify metadata is preserved
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
}
func TestParseTimestamp(t *testing.T) {
testCases := []struct {
name string
input string
expected model.Timestamp
hasError bool
}{
{
name: "Simple minute and second",
input: "01:30",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 0},
hasError: false,
},
{
name: "With milliseconds (1 digit)",
input: "01:30.5",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 500},
hasError: false,
},
{
name: "With milliseconds (2 digits)",
input: "01:30.75",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 750},
hasError: false,
},
{
name: "With milliseconds (3 digits)",
input: "01:30.123",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 123},
hasError: false,
},
{
name: "With hours, minutes, seconds",
input: "01:30:45",
expected: model.Timestamp{Hours: 1, Minutes: 30, Seconds: 45, Milliseconds: 0},
hasError: false,
},
{
name: "With hours, minutes, seconds and milliseconds",
input: "01:30:45.5",
expected: model.Timestamp{Hours: 1, Minutes: 30, Seconds: 45, Milliseconds: 500},
hasError: false,
},
{
name: "Invalid format (single number)",
input: "123",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid format (too many parts)",
input: "01:30:45:67",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid minute (not a number)",
input: "aa:30",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid second (not a number)",
input: "01:bb",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid millisecond (not a number)",
input: "01:30.cc",
expected: model.Timestamp{},
hasError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := ParseTimestamp(tc.input)
if tc.hasError && err == nil {
t.Errorf("Expected error for input '%s', but got none", tc.input)
}
if !tc.hasError && err != nil {
t.Errorf("Unexpected error for input '%s': %v", tc.input, err)
}
if !tc.hasError && result != tc.expected {
t.Errorf("For input '%s', expected %+v, got %+v", tc.input, tc.expected, result)
}
})
}
}
func TestParse_FileErrors(t *testing.T) {
// Test with non-existent file
_, err := Parse("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when parsing non-existent file, got nil")
}
}
func TestParse_EdgeCases(t *testing.T) {
// Test with empty file
tempDir := t.TempDir()
emptyFile := filepath.Join(tempDir, "empty.lrc")
if err := os.WriteFile(emptyFile, []byte{}, 0644); err != nil {
t.Fatalf("Failed to create empty test file: %v", err)
}
lyrics, err := Parse(emptyFile)
if err != nil {
t.Fatalf("Parse failed on empty file: %v", err)
}
if len(lyrics.Timeline) != 0 || len(lyrics.Content) != 0 {
t.Errorf("Expected empty lyrics for empty file, got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with invalid timestamps
invalidFile := filepath.Join(tempDir, "invalid.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
[invalidtime]This should be ignored.
[00:01.00]This is a valid line.
`
if err := os.WriteFile(invalidFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create invalid test file: %v", err)
}
lyrics, err = Parse(invalidFile)
if err != nil {
t.Fatalf("Parse failed on file with invalid timestamps: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid timeline entry, got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with timestamp-only lines (no content)
timestampOnlyFile := filepath.Join(tempDir, "timestamp_only.lrc")
content = `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]
[00:05.00]This has content.
`
if err := os.WriteFile(timestampOnlyFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create timestamp-only test file: %v", err)
}
lyrics, err = Parse(timestampOnlyFile)
if err != nil {
t.Fatalf("Parse failed on file with timestamp-only lines: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid entry (ignoring empty content), got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
}
func TestGenerate_FileError(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
},
Content: []string{
"This is a test line.",
},
}
// Test with invalid path
err := Generate(lyrics, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when generating to invalid path, got nil")
}
}
func TestFormat_FileError(t *testing.T) {
// Test with non-existent file
err := Format("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when formatting non-existent file, got nil")
}
}
func TestConvertToSubtitle_FileError(t *testing.T) {
// Test with non-existent file
_, err := ConvertToSubtitle("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when converting non-existent file, got nil")
}
}
func TestConvertToSubtitle_EdgeCases(t *testing.T) {
// Test with empty lyrics (no content/timeline)
tempDir := t.TempDir()
emptyLyricsFile := filepath.Join(tempDir, "empty_lyrics.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
`
if err := os.WriteFile(emptyLyricsFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create empty lyrics test file: %v", err)
}
subtitle, err := ConvertToSubtitle(emptyLyricsFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on empty lyrics: %v", err)
}
if len(subtitle.Entries) != 0 {
t.Errorf("Expected 0 entries for empty lyrics, got %d", len(subtitle.Entries))
}
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
// Test with more content than timeline entries
moreContentFile := filepath.Join(tempDir, "more_content.lrc")
content = `[ti:Test LRC File]
[00:01.00]This has a timestamp.
This doesn't have a timestamp but is content.
`
if err := os.WriteFile(moreContentFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create more content test file: %v", err)
}
subtitle, err = ConvertToSubtitle(moreContentFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on file with more content than timestamps: %v", err)
}
if len(subtitle.Entries) != 1 {
t.Errorf("Expected 1 entry (only the one with timestamp), got %d", len(subtitle.Entries))
}
}
func TestConvertFromSubtitle_FileError(t *testing.T) {
// Create simple subtitle
subtitle := model.NewSubtitle()
subtitle.Entries = append(subtitle.Entries, model.NewSubtitleEntry())
// Test with invalid path
err := ConvertFromSubtitle(subtitle, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when converting to invalid path, got nil")
}
}