198 lines
7.6 KiB
Go
198 lines
7.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
|
commandkit "go.maximumdirect.net/commandkit"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestPreparedDeliveryFollowsReportingMode(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for name, contents := range map[string]string{
|
|
"config.yml": "version: 3\npipeline:\n stages: []\nreporting:\n enabled: false\n",
|
|
"transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`,
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
flags []string
|
|
file bool
|
|
wantInfo bool
|
|
}{
|
|
{"ordinary", nil, false, false}, {"verbose", []string{"--verbose"}, false, true}, {"quiet file", []string{"--quiet"}, true, false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
input := processInput{Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true}, Transcript: filepath.Join(dir, "transcript.json")}
|
|
if tc.file {
|
|
input.Output = commandkit.Supplied[string]{Value: filepath.Join(dir, "result.json"), Set: true}
|
|
}
|
|
cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
|
loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode())
|
|
if err != nil {
|
|
return commandkit.Usage(err.Error())
|
|
}
|
|
prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output())
|
|
if err != nil {
|
|
return commandkit.Failure(err.Error())
|
|
}
|
|
return finishProcess(inv, input, loaded, prepared, reporting.Snapshot{}, []byte("transcript bytes"), nil)
|
|
}})
|
|
app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var out, stderr bytes.Buffer
|
|
args := append([]string{"deliver"}, tc.flags...)
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &out, Err: &stderr})
|
|
if outcome.Status != 0 {
|
|
t.Fatalf("outcome=%+v err=%s", outcome, stderr.String())
|
|
}
|
|
if tc.file {
|
|
data, err := os.ReadFile(input.Output.Value)
|
|
if err != nil || string(data) != "transcript bytes" {
|
|
t.Fatalf("file=%q err=%v", data, err)
|
|
}
|
|
if out.Len() != 0 {
|
|
t.Fatalf("stdout=%q", out.String())
|
|
}
|
|
} else if out.String() != "transcript bytes" {
|
|
t.Fatalf("stdout=%q", out.String())
|
|
}
|
|
if strings.Contains(stderr.String(), "Completed:") != tc.wantInfo {
|
|
t.Fatalf("stderr=%q", stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeliveryFailureSuppressesCompletionSummary(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Chdir(dir)
|
|
for name, contents := range map[string]string{
|
|
"config.yml": "version: 3\npipeline:\n stages: []\n",
|
|
"transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`,
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
input := processInput{Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true}, Transcript: filepath.Join(dir, "transcript.json")}
|
|
cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
|
loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode())
|
|
if err != nil {
|
|
return commandkit.Failure(err.Error())
|
|
}
|
|
prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output())
|
|
if err != nil {
|
|
return commandkit.Failure(err.Error())
|
|
}
|
|
return finishProcess(inv, input, loaded, prepared, reporting.Snapshot{}, []byte("transcript bytes"), nil)
|
|
}})
|
|
app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var stderr bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"deliver", "--verbose"}, commandkit.Streams{Out: shortProcessWriter{}, Err: &stderr})
|
|
if outcome.Status == 0 || strings.Contains(stderr.String(), "Completed:") {
|
|
t.Fatalf("status=%d stderr=%q", outcome.Status, stderr.String())
|
|
}
|
|
var errs reporting.ErrorsDocument
|
|
var recoveries reporting.RecoveriesDocument
|
|
for path, target := range map[string]any{"errors.yml": &errs, "recoveries.yml": &recoveries} {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := yaml.Unmarshal(data, target); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if errs.Status != reporting.StatusFailed || recoveries.Status != reporting.StatusFailed ||
|
|
errs.Summary.RunErrors == nil || *errs.Summary.RunErrors != 1 ||
|
|
recoveries.Summary.RunErrors == nil || *recoveries.Summary.RunErrors != 1 ||
|
|
len(errs.Errors) != 1 || errs.Errors[0].Reason != "output_failure" {
|
|
t.Fatalf("failed output reports disagree: %+v %+v", errs, recoveries)
|
|
}
|
|
|
|
}
|
|
|
|
func TestCanceledEmptyPipelineLeavesTranscriptAndFinalizesReports(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for name, contents := range map[string]string{
|
|
"config.yml": "version: 3\npipeline: {stages: []}\n",
|
|
"transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`,
|
|
"output.json": "previous transcript",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
input := processInput{
|
|
Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true},
|
|
Transcript: filepath.Join(dir, "transcript.json"),
|
|
Output: commandkit.Supplied[string]{Value: filepath.Join(dir, "output.json"), Set: true},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
|
loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer prepared.Prepared.Output.Close()
|
|
result, err := executePipeline(inv.Context, loaded, prepared, sourceToCanonicalTranscript(loaded.Source), func(error) {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cancel()
|
|
return finishProcess(inv, input, loaded, prepared, result.Snapshot, []byte("new transcript"), nil)
|
|
}})
|
|
app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var stdout, stderr bytes.Buffer
|
|
outcome := app.Execute(ctx, []string{"deliver", "--verbose"}, commandkit.Streams{Out: &stdout, Err: &stderr})
|
|
if outcome.Status == 0 || !errors.Is(outcome.Cause, context.Canceled) || strings.Contains(stderr.String(), "Completed:") {
|
|
t.Fatalf("outcome=%+v stderr=%q", outcome, stderr.String())
|
|
}
|
|
if data, err := os.ReadFile(input.Output.Value); err != nil || string(data) != "previous transcript" {
|
|
t.Fatalf("transcript changed after cancellation: %q, %v", data, err)
|
|
}
|
|
var errorsDoc reporting.ErrorsDocument
|
|
var recoveries reporting.RecoveriesDocument
|
|
for name, target := range map[string]any{"errors.yml": &errorsDoc, "recoveries.yml": &recoveries} {
|
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := yaml.Unmarshal(data, target); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if errorsDoc.Status != reporting.StatusFailed || recoveries.Status != reporting.StatusFailed ||
|
|
len(errorsDoc.Errors) != 1 || errorsDoc.Errors[0].Reason != "canceled" {
|
|
t.Fatalf("reports did not capture cancellation: %+v %+v", errorsDoc, recoveries)
|
|
}
|
|
}
|
|
|
|
type shortProcessWriter struct{}
|
|
|
|
func (shortProcessWriter) Write(data []byte) (int, error) { return len(data) / 2, nil }
|