370 lines
9.5 KiB
Go
370 lines
9.5 KiB
Go
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
|
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
|
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
|
|
)
|
|
|
|
const DefaultBundleID = "weather.daily.brentwood.2026-05-30"
|
|
|
|
var DefaultCreated = time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC)
|
|
|
|
type SourceFile struct {
|
|
Path string
|
|
Data string
|
|
}
|
|
|
|
type BundleOptions struct {
|
|
ID string
|
|
Created time.Time
|
|
Files []SourceFile
|
|
ExtraFiles []SourceFile
|
|
}
|
|
|
|
func DefaultSourceFiles() []SourceFile {
|
|
return []SourceFile{
|
|
{Path: "report.md", Data: "# Report\nSunny.\n"},
|
|
{Path: "summary.txt", Data: "Summary\n"},
|
|
}
|
|
}
|
|
|
|
func ValidManifest(opts BundleOptions) bundle.Manifest {
|
|
files := sourceFiles(opts)
|
|
manifestFiles := make([]bundle.ManifestFile, 0, len(files))
|
|
for _, file := range files {
|
|
data := []byte(file.Data)
|
|
manifestFiles = append(manifestFiles, bundle.ManifestFile{
|
|
Path: file.Path,
|
|
SHA256: bundle.FileDigest(data),
|
|
Size: int64(len(data)),
|
|
})
|
|
}
|
|
manifest := bundle.Manifest{
|
|
SchemaVersion: 1,
|
|
ID: defaultBundleID(opts.ID),
|
|
Created: defaultCreated(opts.Created),
|
|
Files: manifestFiles,
|
|
}
|
|
manifest.Digest = bundle.BundleDigest(manifest.Files)
|
|
return manifest
|
|
}
|
|
|
|
func WriteSourceBundle(t testing.TB, root, relative string, opts BundleOptions) bundle.Manifest {
|
|
t.Helper()
|
|
bundleRoot := filepath.Join(root, filepath.FromSlash(relative))
|
|
if err := os.MkdirAll(bundleRoot, 0o755); err != nil {
|
|
t.Fatalf("mkdir source bundle: %v", err)
|
|
}
|
|
manifest := ValidManifest(opts)
|
|
for _, file := range sourceFiles(opts) {
|
|
path := filepath.Join(bundleRoot, filepath.FromSlash(file.Path))
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("mkdir source file parent: %v", err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(file.Data), 0o600); err != nil {
|
|
t.Fatalf("write source file: %v", err)
|
|
}
|
|
}
|
|
writeJSONFile(t, filepath.Join(bundleRoot, bundle.ManifestName), manifest)
|
|
return manifest
|
|
}
|
|
|
|
func WriteFakeSourceBundle(t testing.TB, backend *fake.Backend, relative string, opts BundleOptions) bundle.Bundle {
|
|
t.Helper()
|
|
manifest := ValidManifest(opts)
|
|
for _, file := range sourceFiles(opts) {
|
|
path := joinStoragePath(t, relative, file.Path)
|
|
if _, err := backend.WriteFile(context.Background(), path, []byte(file.Data), storage.WriteOptions{}); err != nil {
|
|
t.Fatalf("write fake source file: %v", err)
|
|
}
|
|
}
|
|
data, err := json.MarshalIndent(manifest, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("marshal manifest: %v", err)
|
|
}
|
|
manifestPath := joinStoragePath(t, relative, bundle.ManifestName)
|
|
if _, err := backend.WriteFile(context.Background(), manifestPath, append(data, '\n'), storage.WriteOptions{}); err != nil {
|
|
t.Fatalf("write fake manifest: %v", err)
|
|
}
|
|
return bundle.Bundle{RootRelativePath: relative, Manifest: manifest}
|
|
}
|
|
|
|
func WriteFakeFile(t testing.TB, backend *fake.Backend, path, data string) {
|
|
t.Helper()
|
|
if _, err := backend.WriteFile(context.Background(), path, []byte(data), storage.WriteOptions{}); err != nil {
|
|
t.Fatalf("write fake file %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func AssertFakeFile(t testing.TB, backend *fake.Backend, path, want string) {
|
|
t.Helper()
|
|
data, err := backend.ReadFile(context.Background(), path)
|
|
if err != nil {
|
|
t.Fatalf("read fake file %s: %v", path, err)
|
|
}
|
|
if got := string(data); got != want {
|
|
t.Fatalf("fake file %s = %q, want %q", path, got, want)
|
|
}
|
|
}
|
|
|
|
func AssertFakeMissing(t testing.TB, backend *fake.Backend, path string) {
|
|
t.Helper()
|
|
if _, err := backend.Stat(context.Background(), path); !storage.IsNotFound(err) {
|
|
t.Fatalf("fake file %s stat error = %v, want not found", path, err)
|
|
}
|
|
}
|
|
|
|
func WriteMinimalLocalConfig(t testing.TB, sourceRoot, destinationRoot string) string {
|
|
t.Helper()
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+destinationRoot+`
|
|
`)
|
|
}
|
|
|
|
func WriteFanoutLocalConfig(t testing.TB, sourceRoot, firstDestinationRoot, secondDestinationRoot string) string {
|
|
t.Helper()
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive-one
|
|
backend: local
|
|
path: `+firstDestinationRoot+`
|
|
- id: archive-two
|
|
backend: local
|
|
path: `+secondDestinationRoot+`
|
|
`)
|
|
}
|
|
|
|
func WriteLocalConfigWithPublishPolicy(t testing.TB, sourceRoot, destinationRoot string, publishSource, publishHTML bool) string {
|
|
t.Helper()
|
|
transformConfig := ""
|
|
if publishHTML {
|
|
transformConfig = `
|
|
transform:
|
|
markdown_to_html:
|
|
enabled: true
|
|
mode: sidecar`
|
|
}
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+destinationRoot+`
|
|
publish:
|
|
source: `+fmt.Sprintf("%t", publishSource)+`
|
|
html: `+fmt.Sprintf("%t", publishHTML)+transformConfig+`
|
|
`)
|
|
}
|
|
|
|
func WriteLocalConfigWithPathMapping(t testing.TB, sourceRoot, destinationRoot, mode string) string {
|
|
t.Helper()
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+destinationRoot+`
|
|
path_mapping:
|
|
mode: `+mode+`
|
|
`)
|
|
}
|
|
|
|
func WriteLocalConfigWithLinks(t testing.TB, sourceRoot, destinationRoot, pathMapping, baseURL, primary string, publishSource, publishHTML bool, transformMode string) string {
|
|
t.Helper()
|
|
transformConfig := ""
|
|
if publishHTML {
|
|
transformConfig = `
|
|
transform:
|
|
markdown_to_html:
|
|
enabled: true
|
|
mode: ` + transformMode
|
|
}
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+destinationRoot+`
|
|
path_mapping:
|
|
mode: `+pathMapping+`
|
|
links:
|
|
base_url: `+baseURL+`
|
|
primary: `+primary+`
|
|
publish:
|
|
source: `+fmt.Sprintf("%t", publishSource)+`
|
|
html: `+fmt.Sprintf("%t", publishHTML)+transformConfig+`
|
|
`)
|
|
}
|
|
|
|
func WriteLocalConfigWithMarkdownTransform(t testing.TB, sourceRoot, destinationRoot string, publishSource, publishHTML bool, mode, input string) string {
|
|
t.Helper()
|
|
enabled := publishHTML
|
|
inputConfig := ""
|
|
if input != "" {
|
|
inputConfig = `
|
|
input: ` + input
|
|
}
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+destinationRoot+`
|
|
publish:
|
|
source: `+fmt.Sprintf("%t", publishSource)+`
|
|
html: `+fmt.Sprintf("%t", publishHTML)+`
|
|
transform:
|
|
markdown_to_html:
|
|
enabled: `+fmt.Sprintf("%t", enabled)+`
|
|
mode: `+mode+inputConfig+`
|
|
`)
|
|
}
|
|
|
|
func WriteMixedPolicyFanoutLocalConfig(t testing.TB, sourceRoot, archiveDestination, htmlDestination string) string {
|
|
t.Helper()
|
|
return writeConfigFile(t, `
|
|
pipelines:
|
|
- id: reports
|
|
source:
|
|
backend: local
|
|
path: `+sourceRoot+`
|
|
destinations:
|
|
- id: archive
|
|
backend: local
|
|
path: `+archiveDestination+`
|
|
publish:
|
|
source: true
|
|
html: false
|
|
- id: html
|
|
backend: local
|
|
path: `+htmlDestination+`
|
|
publish:
|
|
source: false
|
|
html: true
|
|
transform:
|
|
markdown_to_html:
|
|
enabled: true
|
|
mode: sidecar
|
|
`)
|
|
}
|
|
|
|
func AssertFile(t testing.TB, path, want string) {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read file %s: %v", path, err)
|
|
}
|
|
if got := string(data); got != want {
|
|
t.Fatalf("%s = %q, want %q", path, got, want)
|
|
}
|
|
}
|
|
|
|
func AssertFileContains(t testing.TB, path, want string) {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read file %s: %v", path, err)
|
|
}
|
|
if !strings.Contains(string(data), want) {
|
|
t.Fatalf("%s = %q, want substring %q", path, data, want)
|
|
}
|
|
}
|
|
|
|
func sourceFiles(opts BundleOptions) []SourceFile {
|
|
files := opts.Files
|
|
if files == nil {
|
|
files = DefaultSourceFiles()
|
|
} else {
|
|
files = append([]SourceFile(nil), files...)
|
|
}
|
|
files = append(files, opts.ExtraFiles...)
|
|
return files
|
|
}
|
|
|
|
func writeConfigFile(t testing.TB, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "config.yml")
|
|
if err := os.WriteFile(path, []byte(strings.TrimSpace(body)+"\n"), 0o600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func writeJSONFile(t testing.TB, path string, value any) {
|
|
t.Helper()
|
|
data, err := json.MarshalIndent(value, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("marshal json: %v", err)
|
|
}
|
|
data = append(data, '\n')
|
|
if err := os.WriteFile(path, data, 0o600); err != nil {
|
|
t.Fatalf("write json file: %v", err)
|
|
}
|
|
}
|
|
|
|
func joinStoragePath(t testing.TB, root, path string) string {
|
|
t.Helper()
|
|
joined, err := storage.Join(root, path)
|
|
if err != nil {
|
|
t.Fatalf("join storage path: %v", err)
|
|
}
|
|
return joined
|
|
}
|
|
|
|
func defaultBundleID(value string) string {
|
|
return defaultString(value, DefaultBundleID)
|
|
}
|
|
|
|
func defaultString(value, fallback string) string {
|
|
if value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func defaultCreated(value time.Time) time.Time {
|
|
if value.IsZero() {
|
|
return DefaultCreated
|
|
}
|
|
return value
|
|
}
|