473 lines
14 KiB
Go
473 lines
14 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestValidatePublishTransformPolicy(t *testing.T) {
|
|
tests := publishTransformPolicyCases()
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidatePublishTransformPolicy(tt.publish, tt.transform)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("ValidatePublishTransformPolicy() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("ValidatePublishTransformPolicy() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateChecksPublishTransformPolicy(t *testing.T) {
|
|
tests := publishTransformPolicyCases()
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{
|
|
Backend: BackendLocal,
|
|
Path: "/source",
|
|
},
|
|
Destinations: []Destination{{
|
|
ID: "archive",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Publish: &tt.publish,
|
|
Transform: tt.transform,
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("Validate() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidatePathMapping(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mode string
|
|
wantErr bool
|
|
}{
|
|
{name: "preserve relative", mode: PathMappingPreserveRelative},
|
|
{name: "fixed", mode: PathMappingFixed},
|
|
{name: "invalid", mode: "archive", wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "archive",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
PathMap: PathMapping{Mode: tt.mode},
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("Validate() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateWorkflow(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
workflow string
|
|
wantErr bool
|
|
}{
|
|
{name: "additive", workflow: WorkflowAdditive},
|
|
{name: "replacement", workflow: WorkflowReplacement},
|
|
{name: "invalid", workflow: "append", wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "archive",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Workflow: tt.workflow,
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("Validate() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateWorkflowReportsFieldContext(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "archive",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Workflow: "append",
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
want := "pipelines[0].destinations[0].workflow must be additive or replacement"
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("Validate() error = %q, want %q", err, want)
|
|
}
|
|
}
|
|
|
|
func TestValidateRetentionPolicy(t *testing.T) {
|
|
olderThan := Duration(24 * time.Hour)
|
|
zeroDuration := Duration(0)
|
|
keepZero := 0
|
|
keepThree := 3
|
|
keepNegative := -1
|
|
tests := []struct {
|
|
name string
|
|
retention RetentionPolicy
|
|
wantErr bool
|
|
}{
|
|
{name: "disabled"},
|
|
{name: "older than", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &olderThan}}},
|
|
{name: "keep zero", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepZero}}},
|
|
{name: "keep latest", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepThree}}},
|
|
{name: "combined", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &olderThan, KeepLatest: &keepThree}}},
|
|
{name: "missing policy", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true}}, wantErr: true},
|
|
{name: "zero older than", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &zeroDuration}}, wantErr: true},
|
|
{name: "negative keep latest", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepNegative}}, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "archive",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Retention: tt.retention,
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("Validate() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateLinks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
links *Links
|
|
wantErr bool
|
|
}{
|
|
{name: "absent links"},
|
|
{name: "http", links: &Links{BaseURL: "http://reports.example.com/archive", Primary: LinkPrimaryAuto}},
|
|
{name: "https", links: &Links{BaseURL: "https://reports.example.com/archive/", Primary: LinkPrimaryHTML}},
|
|
{name: "source primary", links: &Links{BaseURL: "https://reports.example.com", Primary: LinkPrimarySource}},
|
|
{name: "missing base", links: &Links{Primary: LinkPrimaryAuto}, wantErr: true},
|
|
{name: "ftp scheme", links: &Links{BaseURL: "ftp://reports.example.com", Primary: LinkPrimaryAuto}, wantErr: true},
|
|
{name: "missing host", links: &Links{BaseURL: "https:///archive", Primary: LinkPrimaryAuto}, wantErr: true},
|
|
{name: "query", links: &Links{BaseURL: "https://reports.example.com/archive?preview=1", Primary: LinkPrimaryAuto}, wantErr: true},
|
|
{name: "fragment", links: &Links{BaseURL: "https://reports.example.com/archive#top", Primary: LinkPrimaryAuto}, wantErr: true},
|
|
{name: "invalid primary", links: &Links{BaseURL: "https://reports.example.com", Primary: "document"}, wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "web",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Links: tt.links,
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("Validate() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateLinksReportsFieldContext(t *testing.T) {
|
|
cfg := Config{Pipelines: []Pipeline{{
|
|
ID: "reports",
|
|
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
|
Destinations: []Destination{{
|
|
ID: "web",
|
|
Backend: BackendLocal,
|
|
Path: "/destination",
|
|
Links: &Links{BaseURL: "https://reports.example.com/archive?preview=1", Primary: LinkPrimaryAuto},
|
|
}},
|
|
}}}
|
|
ApplyDefaults(&cfg)
|
|
err := Validate(cfg)
|
|
if err == nil {
|
|
t.Fatal("Validate() error = nil, want error")
|
|
}
|
|
want := "pipelines[0].destinations[0].links.base_url must not include a query string"
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("Validate() error = %q, want %q", err, want)
|
|
}
|
|
}
|
|
|
|
type publishTransformPolicyCase struct {
|
|
name string
|
|
publish PublishPolicy
|
|
transform Transform
|
|
wantErr bool
|
|
}
|
|
|
|
func publishTransformPolicyCases() []publishTransformPolicyCase {
|
|
return []publishTransformPolicyCase{
|
|
{
|
|
name: "source only allowed",
|
|
publish: PublishPolicy{Source: true},
|
|
},
|
|
{
|
|
name: "html only sidecar allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeSidecar,
|
|
}},
|
|
},
|
|
{
|
|
name: "html only default mode allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
}},
|
|
},
|
|
{
|
|
name: "html only index allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeIndex,
|
|
}},
|
|
},
|
|
{
|
|
name: "html only index input allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeIndex,
|
|
Input: "report.md",
|
|
}},
|
|
},
|
|
{
|
|
name: "html only sidecar css href allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeSidecar,
|
|
CssHref: "/assets/report.css",
|
|
}},
|
|
},
|
|
{
|
|
name: "html only index css href allowed",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeIndex,
|
|
CssHref: "assets/report.css?v=20260614",
|
|
}},
|
|
},
|
|
{
|
|
name: "source and html sidecar allowed",
|
|
publish: PublishPolicy{Source: true, HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeSidecar,
|
|
}},
|
|
},
|
|
{
|
|
name: "no outputs rejected",
|
|
publish: PublishPolicy{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "html without transform rejected",
|
|
publish: PublishPolicy{HTML: true},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "html with disabled transform rejected",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: TransformModeSidecar,
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "html with wrong mode rejected",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: "inline",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "source only enabled transform rejected",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeSidecar,
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "enabled markdown wrong mode rejected",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: "inline",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "sidecar input rejected",
|
|
publish: PublishPolicy{HTML: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: true,
|
|
Mode: TransformModeSidecar,
|
|
Input: "report.md",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "disabled markdown empty mode allowed",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
}},
|
|
},
|
|
{
|
|
name: "disabled markdown sidecar mode allowed",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: TransformModeSidecar,
|
|
}},
|
|
},
|
|
{
|
|
name: "disabled markdown index mode allowed",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: TransformModeIndex,
|
|
}},
|
|
},
|
|
{
|
|
name: "disabled markdown input rejected",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: TransformModeIndex,
|
|
Input: "report.md",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "disabled markdown css href rejected",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: TransformModeSidecar,
|
|
CssHref: "/assets/report.css",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "disabled markdown wrong mode rejected",
|
|
publish: PublishPolicy{Source: true},
|
|
transform: Transform{MarkdownToHTML: &MarkdownToHTML{
|
|
Enabled: false,
|
|
Mode: "inline",
|
|
}},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestValidateCSSHref(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
wantErr bool
|
|
}{
|
|
{name: "empty"},
|
|
{name: "root relative", value: "/assets/report.css"},
|
|
{name: "relative", value: "assets/report.css"},
|
|
{name: "parent relative", value: "../assets/report.css"},
|
|
{name: "query", value: "/assets/report.css?v=20260614"},
|
|
{name: "http", value: "http://example.com/report.css"},
|
|
{name: "https", value: "https://example.com/assets/report.css?v=1"},
|
|
{name: "javascript", value: "javascript:alert(1)", wantErr: true},
|
|
{name: "data", value: "data:text/css,body{}", wantErr: true},
|
|
{name: "file", value: "file:///tmp/report.css", wantErr: true},
|
|
{name: "scheme relative", value: "//example.com/report.css", wantErr: true},
|
|
{name: "userinfo", value: "https://user@example.com/report.css", wantErr: true},
|
|
{name: "fragment", value: "/assets/report.css#main", wantErr: true},
|
|
{name: "space", value: "/assets/report css", wantErr: true},
|
|
{name: "tab", value: "/assets/report\tcss", wantErr: true},
|
|
{name: "newline", value: "/assets/report\ncss", wantErr: true},
|
|
{name: "backslash", value: `assets\report.css`, wantErr: true},
|
|
{name: "less than", value: "/assets/<report>.css", wantErr: true},
|
|
{name: "double quote", value: `/assets/"report".css`, wantErr: true},
|
|
{name: "single quote", value: "/assets/'report'.css", wantErr: true},
|
|
{name: "query only", value: "?v=1", wantErr: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateCSSHref(tt.value)
|
|
if tt.wantErr && err == nil {
|
|
t.Fatal("validateCSSHref() error = nil, want error")
|
|
}
|
|
if !tt.wantErr && err != nil {
|
|
t.Fatalf("validateCSSHref() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|