Add shared test fixture helpers
This commit is contained in:
256
internal/testutil/fixtures.go
Normal file
256
internal/testutil/fixtures.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/state"
|
||||
"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
|
||||
}
|
||||
|
||||
type DestinationStateOptions struct {
|
||||
PipelineID string
|
||||
DestinationID string
|
||||
DistributorVersion string
|
||||
PublishedAt time.Time
|
||||
}
|
||||
|
||||
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 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 WriteDestinationState(t testing.TB, root, relative string, manifest bundle.Manifest, opts DestinationStateOptions) state.DistributorState {
|
||||
t.Helper()
|
||||
bundleRoot := filepath.Join(root, filepath.FromSlash(relative))
|
||||
if err := os.MkdirAll(bundleRoot, 0o755); err != nil {
|
||||
t.Fatalf("mkdir destination bundle: %v", err)
|
||||
}
|
||||
destinationState := DestinationState(manifest, opts)
|
||||
writeJSONFile(t, filepath.Join(bundleRoot, storage.StateFileName), destinationState)
|
||||
return destinationState
|
||||
}
|
||||
|
||||
func DestinationState(manifest bundle.Manifest, opts DestinationStateOptions) state.DistributorState {
|
||||
return state.DistributorState{
|
||||
SchemaVersion: state.SchemaVersion,
|
||||
DistributorVersion: opts.DistributorVersion,
|
||||
PipelineID: defaultString(opts.PipelineID, "reports"),
|
||||
DestinationID: defaultString(opts.DestinationID, "archive"),
|
||||
PublishedAt: defaultPublishedAt(opts.PublishedAt),
|
||||
Source: state.SourceState{Manifest: manifest},
|
||||
Outputs: sourceOutputs(manifest),
|
||||
}
|
||||
}
|
||||
|
||||
func ReadDestinationState(t testing.TB, path string) state.DistributorState {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read destination state: %v", err)
|
||||
}
|
||||
destinationState, err := state.Parse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("parse destination state: %v", err)
|
||||
}
|
||||
return destinationState
|
||||
}
|
||||
|
||||
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 sourceOutputs(manifest bundle.Manifest) []state.OutputFile {
|
||||
outputs := make([]state.OutputFile, 0, len(manifest.Files))
|
||||
for _, file := range manifest.Files {
|
||||
outputs = append(outputs, state.OutputFile{
|
||||
Path: file.Path,
|
||||
Kind: state.OutputKindSource,
|
||||
SourcePath: file.Path,
|
||||
SHA256: file.SHA256,
|
||||
Size: file.Size,
|
||||
})
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func defaultPublishedAt(value time.Time) time.Time {
|
||||
if value.IsZero() {
|
||||
return time.Date(2026, 5, 30, 11, 12, 0, 0, time.UTC)
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user