Files
distributor/internal/publish/output_test.go

361 lines
12 KiB
Go

package publish
import (
"context"
"testing"
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/config"
"gitea.maximumdirect.net/eric/distributor/internal/state"
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
"gitea.maximumdirect.net/eric/distributor/internal/transform"
)
func TestOutputStateProjection(t *testing.T) {
sourceOutput := Output{
SourcePath: "report.md",
DestinationPath: "report.md",
Kind: state.OutputKindSource,
URL: "https://reports.example.com/report.md",
SHA256: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Size: 123,
}
sourceState := sourceOutput.StateOutputFile()
if sourceState.Path != "report.md" || sourceState.Kind != state.OutputKindSource || sourceState.SourcePath != "report.md" || sourceState.URL != sourceOutput.URL || sourceState.SHA256 != sourceOutput.SHA256 || sourceState.Size != sourceOutput.Size {
t.Fatalf("source state output = %#v", sourceState)
}
generatedOutput := Output{
SourcePath: "report.md",
DestinationPath: "report.html",
Kind: state.OutputKindGenerated,
Transform: transform.MarkdownToHTML,
URL: "https://reports.example.com/report.html",
SHA256: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
Size: 456,
}
generatedState := generatedOutput.StateOutputFile()
if generatedState.Path != "report.html" || generatedState.Kind != state.OutputKindGenerated || generatedState.SourcePath != "report.md" || generatedState.Transform != transform.MarkdownToHTML || generatedState.URL != generatedOutput.URL || generatedState.SHA256 != generatedOutput.SHA256 || generatedState.Size != generatedOutput.Size {
t.Fatalf("generated state output = %#v", generatedState)
}
}
func TestOutputSliceProjections(t *testing.T) {
outputs := []Output{
{SourcePath: "report.md", DestinationPath: "report.md", Kind: state.OutputKindSource, SHA256: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Size: 1},
{SourcePath: "report.md", DestinationPath: "report.html", Kind: state.OutputKindGenerated, Transform: transform.MarkdownToHTML, URL: "https://reports.example.com/report.html", SHA256: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Size: 2},
}
stateOutputs := StateOutputFiles(outputs)
if len(stateOutputs) != 2 || stateOutputs[1].Path != "report.html" || stateOutputs[1].Transform != transform.MarkdownToHTML || stateOutputs[1].URL != outputs[1].URL {
t.Fatalf("state outputs = %#v", stateOutputs)
}
paths := ManagedOutputPaths(outputs)
if len(paths) != 2 || paths[0] != "report.md" || paths[1] != "report.html" {
t.Fatalf("managed paths = %#v", paths)
}
}
func TestPlanOutputsRejectsCollision(t *testing.T) {
sourceBackend := fake.New()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "", testutil.BundleOptions{
Files: []testutil.SourceFile{
{Path: "report.md", Data: "# Report\n"},
{Path: "report.html", Data: "<p>source html</p>\n"},
},
})
_, err := PlanOutputs(context.Background(), Request{
SourceBackend: sourceBackend,
SourceBundle: sourceBundle,
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()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "", testutil.BundleOptions{
Files: []testutil.SourceFile{{Path: "summary.txt", Data: "Summary\n"}},
})
_, err := PlanOutputs(context.Background(), Request{
SourceBackend: sourceBackend,
SourceBundle: sourceBundle,
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 TestPlanOutputsPassesMarkdownOptions(t *testing.T) {
data := []byte("<p>Generated</p>\n")
transformer := &recordingTransformer{outputs: []transform.Output{{
Path: "index.html",
SourcePath: "report.md",
Transform: transform.MarkdownToHTML,
Data: data,
SHA256: bundle.FileDigest(data),
Size: int64(len(data)),
}}}
_, err := PlanOutputs(context.Background(), Request{
Publish: config.PublishPolicy{HTML: true},
Transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: config.TransformModeIndex,
Input: "report.md",
CssHref: "/assets/report.css",
}},
Transformers: testResolver{transform.MarkdownToHTML: transformer},
})
if err != nil {
t.Fatalf("PlanOutputs() error = %v", err)
}
if transformer.request.Markdown.Mode != config.TransformModeIndex || transformer.request.Markdown.Input != "report.md" || transformer.request.Markdown.CssHref != "/assets/report.css" {
t.Fatalf("markdown options = %#v, want index/report.md with css href", transformer.request.Markdown)
}
}
func TestBuildRejectsHTMLWithoutTransform(t *testing.T) {
sourceBackend := fake.New()
destinationBackend := fake.New()
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "", testutil.BundleOptions{
Files: []testutil.SourceFile{{Path: "report.md", Data: "# Report\n"}},
})
_, err := Build(context.Background(), Request{
PipelineID: "reports",
DestinationID: "archive",
SourceBackend: sourceBackend,
DestinationBackend: destinationBackend,
DestinationBundlePath: "",
SourceBundle: sourceBundle,
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: "html only index allowed",
publish: config.PublishPolicy{HTML: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: config.TransformModeIndex,
}},
},
{
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: "source only enabled transform rejected",
publish: config.PublishPolicy{Source: true},
transform: config.Transform{MarkdownToHTML: &config.MarkdownToHTML{
Enabled: true,
Mode: config.TransformModeSidecar,
}},
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
}
type recordingTransformer struct {
outputs []transform.Output
request transform.Request
err error
}
func (t *recordingTransformer) Generate(_ context.Context, req transform.Request) ([]transform.Output, error) {
t.request = req
return t.outputs, t.err
}