Add shared test fixture helpers

This commit is contained in:
2026-05-31 03:35:46 +00:00
parent a973d0912d
commit 9c80e7179e
3 changed files with 267 additions and 47 deletions

View File

@@ -5,18 +5,17 @@ import (
"fmt"
"io"
"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/testutil"
)
func TestExecuteCleansUpAfterWriteFailure(t *testing.T) {
sourceBackend := fake.New()
destinationBackend := &failingBackend{Backend: fake.New(), failPath: "summary.txt"}
sourceBundle := writeFakeSourceBundle(t, sourceBackend)
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "", testutil.BundleOptions{})
req := Request{
PipelineID: "reports",
DestinationID: "archive",
@@ -63,29 +62,3 @@ func (b *failingBackend) WriteFrom(ctx context.Context, path string, r io.Reader
}
return b.Backend.WriteFrom(ctx, path, r, opts)
}
func writeFakeSourceBundle(t *testing.T, backend *fake.Backend) bundle.Bundle {
t.Helper()
files := []struct {
path string
data string
}{
{path: "report.md", data: "# Report\nSunny.\n"},
{path: "summary.txt", data: "Summary\n"},
}
manifestFiles := make([]bundle.ManifestFile, 0, len(files))
for _, file := range files {
if _, err := backend.WriteFile(context.Background(), file.path, []byte(file.data), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
manifestFiles = append(manifestFiles, bundle.ManifestFile{Path: file.path, SHA256: bundle.FileDigest([]byte(file.data)), Size: int64(len(file.data))})
}
manifest := bundle.Manifest{
SchemaVersion: 1,
ID: "weather.daily.brentwood.2026-05-30",
Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC),
Files: manifestFiles,
}
manifest.Digest = bundle.BundleDigest(manifest.Files)
return bundle.Bundle{Manifest: manifest}
}

View 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
}

View File

@@ -9,6 +9,7 @@ import (
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/storage"
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
"gitea.maximumdirect.net/eric/distributor/internal/transform"
)
@@ -92,22 +93,12 @@ func TestGenerateDeterministicOutput(t *testing.T) {
func markdownFixture(t *testing.T, markdown string) (*fake.Backend, bundle.Bundle) {
t.Helper()
backend := fake.New()
if _, err := backend.WriteFile(context.Background(), "report.md", []byte(markdown), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
if _, err := backend.WriteFile(context.Background(), "summary.txt", []byte("Summary\n"), storage.WriteOptions{}); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
files := []bundle.ManifestFile{
{Path: "report.md", SHA256: bundle.FileDigest([]byte(markdown)), Size: int64(len(markdown))},
{Path: "summary.txt", SHA256: bundle.FileDigest([]byte("Summary\n")), Size: 8},
}
manifest := bundle.Manifest{
SchemaVersion: 1,
sourceBundle := testutil.WriteFakeSourceBundle(t, backend, "", testutil.BundleOptions{
ID: "bundle",
Created: time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC),
Files: files,
}
manifest.Digest = bundle.BundleDigest(manifest.Files)
return backend, bundle.Bundle{Manifest: manifest}
Files: []testutil.SourceFile{
{Path: "report.md", Data: markdown},
{Path: "summary.txt", Data: "Summary\n"},
},
})
return backend, sourceBundle
}