302 lines
8.3 KiB
Go
302 lines
8.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/seriatim/internal/config"
|
|
"gitea.maximumdirect.net/eric/seriatim/schema"
|
|
)
|
|
|
|
func TestTrimKeepModeEndToEnd(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "2,4",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("trim failed: %v", err)
|
|
}
|
|
|
|
var transcript schema.Transcript
|
|
readJSON(t, output, &transcript)
|
|
if len(transcript.Segments) != 2 {
|
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
|
}
|
|
if transcript.Segments[0].Text != "two" || transcript.Segments[1].Text != "four" {
|
|
t.Fatalf("unexpected kept text order: %#v", transcript.Segments)
|
|
}
|
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
|
}
|
|
|
|
func TestTrimRemoveModeEndToEnd(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--remove", "2,4",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("trim failed: %v", err)
|
|
}
|
|
|
|
var transcript schema.Transcript
|
|
readJSON(t, output, &transcript)
|
|
if len(transcript.Segments) != 2 {
|
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
|
}
|
|
if transcript.Segments[0].Text != "one" || transcript.Segments[1].Text != "three" {
|
|
t.Fatalf("unexpected remaining text order: %#v", transcript.Segments)
|
|
}
|
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
|
}
|
|
|
|
func TestTrimMutualExclusionFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "1",
|
|
"--remove", "2",
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected mutual exclusion error")
|
|
}
|
|
if !strings.Contains(err.Error(), "mutually exclusive") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTrimMissingSelectionFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected selection flag error")
|
|
}
|
|
if !strings.Contains(err.Error(), "exactly one of --keep or --remove is required") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTrimInvalidSelectedIDFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "99",
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected missing selected ID error")
|
|
}
|
|
if !strings.Contains(err.Error(), "does not exist") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTrimOmittedOutputSchemaPreservesInputSchema(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimMinimalFixture(t, dir, "input-minimal.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "1",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("trim failed: %v", err)
|
|
}
|
|
|
|
var transcript schema.MinimalTranscript
|
|
readJSON(t, output, &transcript)
|
|
if transcript.Metadata.OutputSchema != config.OutputSchemaMinimal {
|
|
t.Fatalf("output_schema = %q, want %q", transcript.Metadata.OutputSchema, config.OutputSchemaMinimal)
|
|
}
|
|
if len(transcript.Segments) != 1 || transcript.Segments[0].ID != 1 {
|
|
t.Fatalf("unexpected minimal trim output: %#v", transcript.Segments)
|
|
}
|
|
}
|
|
|
|
func TestTrimExplicitOutputSchemaChangesOutputSchema(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "1,3",
|
|
"--output-schema", config.OutputSchemaMinimal,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("trim failed: %v", err)
|
|
}
|
|
|
|
var transcript schema.MinimalTranscript
|
|
readJSON(t, output, &transcript)
|
|
if transcript.Metadata.OutputSchema != config.OutputSchemaMinimal {
|
|
t.Fatalf("output_schema = %q, want %q", transcript.Metadata.OutputSchema, config.OutputSchemaMinimal)
|
|
}
|
|
if len(transcript.Segments) != 2 {
|
|
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
|
}
|
|
assertSequentialIDs(t, []int{transcript.Segments[0].ID, transcript.Segments[1].ID})
|
|
}
|
|
|
|
func TestTrimAllowEmptyBehavior(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeTrimFullFixture(t, dir, "input.json")
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--remove", "1-4",
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected empty-output error")
|
|
}
|
|
if !strings.Contains(err.Error(), "empty transcript") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
err = executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--remove", "1-4",
|
|
"--allow-empty",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("trim with --allow-empty failed: %v", err)
|
|
}
|
|
|
|
var transcript schema.Transcript
|
|
readJSON(t, output, &transcript)
|
|
if len(transcript.Segments) != 0 {
|
|
t.Fatalf("segment count = %d, want 0", len(transcript.Segments))
|
|
}
|
|
}
|
|
|
|
func TestTrimRejectsNonSeriatimInputArtifacts(t *testing.T) {
|
|
dir := t.TempDir()
|
|
input := writeJSONFile(t, dir, "raw-whisperx.json", `{
|
|
"segments": [
|
|
{"start": 1, "end": 2, "text": "hello"}
|
|
]
|
|
}`)
|
|
output := filepath.Join(dir, "trimmed.json")
|
|
|
|
err := executeTrim(
|
|
"--input-file", input,
|
|
"--output-file", output,
|
|
"--keep", "1",
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected invalid artifact error")
|
|
}
|
|
if !strings.Contains(err.Error(), "not a valid seriatim output artifact") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func executeTrim(args ...string) error {
|
|
cmd := NewRootCommand()
|
|
cmd.SetArgs(append([]string{"trim"}, args...))
|
|
return cmd.Execute()
|
|
}
|
|
|
|
func writeTrimFullFixture(t *testing.T, dir string, name string) string {
|
|
t.Helper()
|
|
|
|
first := 10
|
|
second := 20
|
|
third := 30
|
|
fourth := 40
|
|
value := schema.Transcript{
|
|
Metadata: schema.Metadata{
|
|
Application: "seriatim",
|
|
Version: "v-test",
|
|
InputReader: "json-files",
|
|
InputFiles: []string{"a.json"},
|
|
PreprocessingModules: []string{"validate-raw"},
|
|
PostprocessingModules: []string{"assign-ids"},
|
|
OutputModules: []string{"json"},
|
|
},
|
|
Segments: []schema.Segment{
|
|
{ID: 1, Source: "a.json", SourceSegmentIndex: &first, SourceRef: "a.json#10", Speaker: "A", Start: 1, End: 2, Text: "one", OverlapGroupID: 9},
|
|
{ID: 2, Source: "a.json", SourceSegmentIndex: &second, SourceRef: "a.json#20", Speaker: "B", Start: 2, End: 3, Text: "two", OverlapGroupID: 9},
|
|
{ID: 3, Source: "a.json", SourceSegmentIndex: &third, SourceRef: "a.json#30", Speaker: "C", Start: 4, End: 5, Text: "three", OverlapGroupID: 10},
|
|
{ID: 4, Source: "a.json", SourceSegmentIndex: &fourth, SourceRef: "a.json#40", Speaker: "D", Start: 5, End: 6, Text: "four", OverlapGroupID: 10},
|
|
},
|
|
OverlapGroups: []schema.OverlapGroup{
|
|
{ID: 9, Start: 1, End: 3, Segments: []string{"a.json#10", "a.json#20"}, Speakers: []string{"A", "B"}, Class: "unknown", Resolution: "unresolved"},
|
|
},
|
|
}
|
|
|
|
return writeTrimArtifactFile(t, dir, name, value)
|
|
}
|
|
|
|
func writeTrimMinimalFixture(t *testing.T, dir string, name string) string {
|
|
t.Helper()
|
|
|
|
value := schema.MinimalTranscript{
|
|
Metadata: schema.MinimalMetadata{
|
|
Application: "seriatim",
|
|
Version: "v-test",
|
|
OutputSchema: config.OutputSchemaMinimal,
|
|
},
|
|
Segments: []schema.MinimalSegment{
|
|
{ID: 1, Start: 1, End: 2, Speaker: "A", Text: "one"},
|
|
{ID: 2, Start: 2, End: 3, Speaker: "B", Text: "two"},
|
|
},
|
|
}
|
|
|
|
return writeTrimArtifactFile(t, dir, name, value)
|
|
}
|
|
|
|
func writeTrimArtifactFile(t *testing.T, dir string, name string, value any) string {
|
|
t.Helper()
|
|
|
|
data, err := json.MarshalIndent(value, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("marshal fixture: %v", err)
|
|
}
|
|
path := filepath.Join(dir, name)
|
|
if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func assertSequentialIDs(t *testing.T, ids []int) {
|
|
t.Helper()
|
|
for index, id := range ids {
|
|
want := index + 1
|
|
if id != want {
|
|
t.Fatalf("id at index %d = %d, want %d", index, id, want)
|
|
}
|
|
}
|
|
}
|