- WriteReport generiše markdown izveštaj u reports/ - formatDuration, truncateOutput za formatiranje - Kreira folder ako ne postoji, skraćuje output na 50 linija - 10 reporter testova — svi prolaze - T03 premešten u done/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
210 lines
4.9 KiB
Go
210 lines
4.9 KiB
Go
package supervisor
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func makeTestData(allPassed bool) (Task, RunResult, VerifyResult) {
|
|
task := Task{
|
|
ID: "T07",
|
|
Title: "Test feature",
|
|
Agent: "coder",
|
|
Model: "Sonnet",
|
|
Description: "Implement test feature.",
|
|
}
|
|
|
|
run := RunResult{
|
|
TaskID: "T07",
|
|
StartedAt: time.Date(2026, 2, 20, 10, 15, 0, 0, time.UTC),
|
|
FinishedAt: time.Date(2026, 2, 20, 10, 23, 45, 0, time.UTC),
|
|
Duration: 8*time.Minute + 45*time.Second,
|
|
ExitCode: 0,
|
|
Output: "Task completed successfully.\nAll tests passed.",
|
|
}
|
|
|
|
verify := VerifyResult{
|
|
Build: CheckResult{
|
|
Name: "build", Status: "pass",
|
|
Duration: 300 * time.Millisecond,
|
|
},
|
|
Vet: CheckResult{
|
|
Name: "vet", Status: "pass",
|
|
Duration: 200 * time.Millisecond,
|
|
},
|
|
Test: CheckResult{
|
|
Name: "test", Status: "pass",
|
|
Duration: 2100 * time.Millisecond,
|
|
TestCount: 12,
|
|
},
|
|
AllPassed: allPassed,
|
|
}
|
|
|
|
if !allPassed {
|
|
verify.Test.Status = "fail"
|
|
run.ExitCode = 1
|
|
}
|
|
|
|
return task, run, verify
|
|
}
|
|
|
|
func TestWriteReport_CreatesFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reportsDir := filepath.Join(dir, "reports")
|
|
|
|
task, run, verify := makeTestData(true)
|
|
|
|
path, err := WriteReport(task, run, verify, reportsDir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("report file not found: %v", err)
|
|
}
|
|
|
|
expectedFilename := "T07-report.md"
|
|
if filepath.Base(path) != expectedFilename {
|
|
t.Errorf("expected filename %s, got %s", expectedFilename, filepath.Base(path))
|
|
}
|
|
}
|
|
|
|
func TestWriteReport_Content(t *testing.T) {
|
|
dir := t.TempDir()
|
|
task, run, verify := makeTestData(true)
|
|
|
|
path, err := WriteReport(task, run, verify, dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read report: %v", err)
|
|
}
|
|
content := string(data)
|
|
|
|
checks := []struct {
|
|
name string
|
|
substr string
|
|
}{
|
|
{"header", "# T07: Test feature"},
|
|
{"date format", "2026-02-20 10:15"},
|
|
{"duration", "8m 45s"},
|
|
{"status passed", "✅ Završen"},
|
|
{"build check", "✅ build"},
|
|
{"vet check", "✅ vet"},
|
|
{"test count", "12 testova"},
|
|
{"agent output section", "## Agent output"},
|
|
{"output content", "Task completed successfully"},
|
|
}
|
|
|
|
for _, c := range checks {
|
|
if !strings.Contains(content, c.substr) {
|
|
t.Errorf("%s: expected %q in report, got:\n%s", c.name, c.substr, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteReport_FailedStatus(t *testing.T) {
|
|
dir := t.TempDir()
|
|
task, run, verify := makeTestData(false)
|
|
|
|
path, err := WriteReport(task, run, verify, dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(path)
|
|
content := string(data)
|
|
|
|
if !strings.Contains(content, "❌ Neuspešan") {
|
|
t.Errorf("expected '❌ Neuspešan' in report, got:\n%s", content)
|
|
}
|
|
if !strings.Contains(content, "❌ test") {
|
|
t.Errorf("expected '❌ test' in report, got:\n%s", content)
|
|
}
|
|
}
|
|
|
|
func TestWriteReport_CreatesDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reportsDir := filepath.Join(dir, "deep", "nested", "reports")
|
|
|
|
task, run, verify := makeTestData(true)
|
|
|
|
path, err := WriteReport(task, run, verify, reportsDir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("report file not found: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTruncateOutput_Short(t *testing.T) {
|
|
output := "line1\nline2\nline3\n"
|
|
result := truncateOutput(output, 50)
|
|
|
|
if strings.Contains(result, "skraćeno") {
|
|
t.Error("should not truncate short output")
|
|
}
|
|
if !strings.Contains(result, "line1") {
|
|
t.Error("expected all lines present")
|
|
}
|
|
}
|
|
|
|
func TestTruncateOutput_Long(t *testing.T) {
|
|
var lines []string
|
|
for i := 1; i <= 80; i++ {
|
|
lines = append(lines, "line "+string(rune('0'+i/10))+string(rune('0'+i%10)))
|
|
}
|
|
output := strings.Join(lines, "\n")
|
|
|
|
result := truncateOutput(output, 50)
|
|
|
|
if !strings.Contains(result, "skraćeno") {
|
|
t.Error("expected truncation marker")
|
|
}
|
|
if !strings.Contains(result, "poslednjih 50 od 80") {
|
|
t.Errorf("expected 'poslednjih 50 od 80' in: %s", result)
|
|
}
|
|
// First lines should be gone
|
|
if strings.Contains(result, "line 01") {
|
|
t.Error("expected first lines to be removed")
|
|
}
|
|
}
|
|
|
|
func TestTruncateOutput_Empty(t *testing.T) {
|
|
result := truncateOutput("", 50)
|
|
if !strings.Contains(result, "nema outputa") {
|
|
t.Errorf("expected 'nema outputa', got: %s", result)
|
|
}
|
|
}
|
|
|
|
func TestFormatDuration_Minutes(t *testing.T) {
|
|
d := 8*time.Minute + 45*time.Second
|
|
result := formatDuration(d)
|
|
if result != "8m 45s" {
|
|
t.Errorf("expected '8m 45s', got %q", result)
|
|
}
|
|
}
|
|
|
|
func TestFormatDuration_SecondsOnly(t *testing.T) {
|
|
d := 45 * time.Second
|
|
result := formatDuration(d)
|
|
if result != "45s" {
|
|
t.Errorf("expected '45s', got %q", result)
|
|
}
|
|
}
|
|
|
|
func TestFormatDuration_Zero(t *testing.T) {
|
|
result := formatDuration(0)
|
|
if result != "0s" {
|
|
t.Errorf("expected '0s', got %q", result)
|
|
}
|
|
}
|