Files
distributor/internal/publish/output_test.go

285 lines
9.3 KiB
Go

package publish
import (
"context"
"testing"
"time"
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/config"
"gitea.maximumdirect.net/eric/distributor/internal/storage"
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
"gitea.maximumdirect.net/eric/distributor/internal/transform"
)
func TestPlanOutputsRejectsCollision(t *testing.T) {
sourceBackend := fake.New()
if _, err := sourceBackend.WriteFile(context.Background(), "report.md", []byte("# Report\n"), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile report.md error = %v", err)
}
if _, err := sourceBackend.WriteFile(context.Background(), "report.html", []byte("<p>source html</p>\n"), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile report.html error = %v", err)
}
reportDigest := bundle.FileDigest([]byte("# Report\n"))
htmlDigest := bundle.FileDigest([]byte("<p>source html</p>\n"))
files := []bundle.ManifestFile{
{Path: "report.md", SHA256: reportDigest, Size: 9},
{Path: "report.html", SHA256: htmlDigest, Size: 19},
}
_, err := PlanOutputs(context.Background(), Request{
SourceBackend: sourceBackend,
SourceBundle: bundle.Bundle{
Manifest: bundle.Manifest{
SchemaVersion: 1,
ID: "bundle",
Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC),
Digest: bundle.BundleDigest(files),
Files: files,
},
},
Publish: config.PublishPolicy{Source: true, HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{Enabled: true, Mode: config.TransformModeSidecar}},
Transformers: testResolver{transform.MarkdownToHTML: testTransformer{outputs: []transform.Output{{
Path: "report.html",
SourcePath: "report.md",
Transform: transform.MarkdownToHTML,
Data: []byte("<p>Report</p>\n"),
SHA256: bundle.FileDigest([]byte("<p>Report</p>\n")),
Size: int64(len("<p>Report</p>\n")),
}}}},
})
if err == nil {
t.Fatal("PlanSourceOutputs() error = nil, want collision")
}
}
func TestPlanOutputsRejectsHTMLWithoutMarkdown(t *testing.T) {
sourceBackend := fake.New()
if _, err := sourceBackend.WriteFile(context.Background(), "summary.txt", []byte("Summary\n"), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile summary.txt error = %v", err)
}
files := []bundle.ManifestFile{{Path: "summary.txt", SHA256: bundle.FileDigest([]byte("Summary\n")), Size: 8}}
_, err := PlanOutputs(context.Background(), Request{
SourceBackend: sourceBackend,
SourceBundle: bundle.Bundle{Manifest: bundle.Manifest{
SchemaVersion: 1,
ID: "bundle",
Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC),
Digest: bundle.BundleDigest(files),
Files: files,
}},
Publish: config.PublishPolicy{HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{Enabled: true, Mode: config.TransformModeSidecar}},
Transformers: testResolver{
transform.MarkdownToHTML: testTransformer{},
},
})
if err == nil {
t.Fatal("PlanOutputs() error = nil, want no markdown failure")
}
}
func TestPlanOutputsRejectsHTMLWithoutTransformerResolver(t *testing.T) {
_, err := PlanOutputs(context.Background(), Request{
Publish: config.PublishPolicy{HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{Enabled: true, Mode: config.TransformModeSidecar}},
})
if err == nil {
t.Fatal("PlanOutputs() error = nil, want resolver failure")
}
}
func TestPlanOutputsRejectsMissingMarkdownTransformer(t *testing.T) {
_, err := PlanOutputs(context.Background(), Request{
Publish: config.PublishPolicy{HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{Enabled: true, Mode: config.TransformModeSidecar}},
Transformers: testResolver{},
})
if err == nil {
t.Fatal("PlanOutputs() error = nil, want missing transformer failure")
}
}
func TestPlanOutputsUsesRegisteredTransformer(t *testing.T) {
data := []byte("<p>Generated</p>\n")
outputs, err := PlanOutputs(context.Background(), Request{
Publish: config.PublishPolicy{HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{Enabled: true, Mode: config.TransformModeSidecar}},
Transformers: testResolver{transform.MarkdownToHTML: testTransformer{outputs: []transform.Output{{
Path: "report.html",
SourcePath: "report.md",
Transform: transform.MarkdownToHTML,
Data: data,
SHA256: bundle.FileDigest(data),
Size: int64(len(data)),
}}}},
})
if err != nil {
t.Fatalf("PlanOutputs() error = %v", err)
}
if got, want := len(outputs), 1; got != want {
t.Fatalf("output count = %d, want %d", got, want)
}
if outputs[0].DestinationPath != "report.html" || string(outputs[0].Data) != string(data) {
t.Fatalf("output = %#v", outputs[0])
}
}
func TestBuildRejectsHTMLWithoutTransform(t *testing.T) {
sourceBackend := fake.New()
destinationBackend := fake.New()
if _, err := sourceBackend.WriteFile(context.Background(), "report.md", []byte("# Report\n"), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile report.md error = %v", err)
}
files := []bundle.ManifestFile{{Path: "report.md", SHA256: bundle.FileDigest([]byte("# Report\n")), Size: 9}}
_, err := Build(context.Background(), Request{
PipelineID: "reports",
DestinationID: "archive",
SourceBackend: sourceBackend,
DestinationBackend: destinationBackend,
DestinationBundlePath: "",
SourceBundle: bundle.Bundle{Manifest: bundle.Manifest{
SchemaVersion: 1,
ID: "bundle",
Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC),
Digest: bundle.BundleDigest(files),
Files: files,
}},
Publish: config.PublishPolicy{HTML: true},
Transfer: config.TransferPolicy{
OnDestinationSame: config.TransferActionSkip,
OnDestinationOlder: config.TransferActionReplace,
OnDestinationNewer: config.TransferActionSkip,
OnConflict: config.TransferActionFail,
},
})
if err == nil {
t.Fatal("Build() error = nil, want missing transform error")
}
}
func TestValidateRequestChecksPublishTransformPolicy(t *testing.T) {
tests := []struct {
name string
publish config.PublishPolicy
transform config.Transform
wantErr bool
}{
{
name: "source only allowed",
publish: config.PublishPolicy{Source: true},
},
{
name: "html only sidecar allowed",
publish: config.PublishPolicy{HTML: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: config.TransformModeSidecar,
}},
},
{
name: "source and html sidecar allowed",
publish: config.PublishPolicy{Source: true, HTML: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: config.TransformModeSidecar,
}},
},
{
name: "no outputs rejected",
publish: config.PublishPolicy{},
wantErr: true,
},
{
name: "html without transform rejected",
publish: config.PublishPolicy{HTML: true},
wantErr: true,
},
{
name: "html with disabled transform rejected",
publish: config.PublishPolicy{HTML: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: false,
Mode: config.TransformModeSidecar,
}},
wantErr: true,
},
{
name: "html with wrong mode rejected",
publish: config.PublishPolicy{HTML: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: "inline",
}},
wantErr: true,
},
{
name: "enabled markdown wrong mode rejected",
publish: config.PublishPolicy{Source: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: "inline",
}},
wantErr: true,
},
{
name: "disabled markdown empty mode allowed",
publish: config.PublishPolicy{Source: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: false,
}},
},
{
name: "disabled markdown sidecar mode allowed",
publish: config.PublishPolicy{Source: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: false,
Mode: config.TransformModeSidecar,
}},
},
{
name: "disabled markdown wrong mode rejected",
publish: config.PublishPolicy{Source: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: false,
Mode: "inline",
}},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateRequest(Request{
PipelineID: "reports",
DestinationID: "archive",
SourceBackend: fake.New(),
DestinationBackend: fake.New(),
Publish: tt.publish,
Transform: tt.transform,
})
if tt.wantErr && err == nil {
t.Fatal("validateRequest() error = nil, want error")
}
if !tt.wantErr && err != nil {
t.Fatalf("validateRequest() error = %v", err)
}
})
}
}
type testResolver map[string]transform.Transformer
func (r testResolver) Get(name string) (transform.Transformer, bool) {
transformer, ok := r[name]
return transformer, ok
}
type testTransformer struct {
outputs []transform.Output
err error
}
func (t testTransformer) Generate(context.Context, transform.Request) ([]transform.Output, error) {
return t.outputs, t.err
}