Add distributor upload adapter
This commit is contained in:
243
internal/adapters/distributor/client_test.go
Normal file
243
internal/adapters/distributor/client_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package distributor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
distributorupload "gitea.maximumdirect.net/eric/distributor/pkg/upload"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
)
|
||||
|
||||
func TestUploadUsesConfiguredClientAndSingleFile(t *testing.T) {
|
||||
cfg := config.Defaults().Notify.Distributor
|
||||
cfg.Endpoint = "https://distributor.example.test"
|
||||
cfg.TokenEnv = "DISTRIBUTOR_UPLOAD_TOKEN"
|
||||
cfg.Timeout = 15 * time.Second
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
|
||||
factory := &fakeUploadFactory{
|
||||
client: &fakeUploadClient{
|
||||
result: uploadFilesResult{RunID: "run-123", Status: "accepted"},
|
||||
},
|
||||
}
|
||||
client := newClient(cfg, factory.newClient)
|
||||
|
||||
result, err := client.Upload(context.Background(), UploadRequest{
|
||||
BundleID: "weatherreporter.home.daily.run",
|
||||
IdempotencyKey: "weatherreporter.home.daily.run",
|
||||
SourcePath: "/tmp/report.md",
|
||||
BundlePath: "daily.md",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Upload() error = %v", err)
|
||||
}
|
||||
if result.RunID != "run-123" || result.Status != "accepted" {
|
||||
t.Fatalf("result = %#v, want accepted run", result)
|
||||
}
|
||||
if factory.endpoint != cfg.Endpoint {
|
||||
t.Fatalf("factory endpoint = %q, want %q", factory.endpoint, cfg.Endpoint)
|
||||
}
|
||||
if factory.token != "secret-token" {
|
||||
t.Fatalf("factory token = %q, want secret-token", factory.token)
|
||||
}
|
||||
if factory.timeout != 15*time.Second {
|
||||
t.Fatalf("factory timeout = %s, want 15s", factory.timeout)
|
||||
}
|
||||
got := factory.client.opts
|
||||
if got.BundleID != "weatherreporter.home.daily.run" {
|
||||
t.Fatalf("BundleID = %q, want weatherreporter.home.daily.run", got.BundleID)
|
||||
}
|
||||
if got.IdempotencyKey != "weatherreporter.home.daily.run" {
|
||||
t.Fatalf("IdempotencyKey = %q, want weatherreporter.home.daily.run", got.IdempotencyKey)
|
||||
}
|
||||
if got.SourcePath != "/tmp/report.md" {
|
||||
t.Fatalf("SourcePath = %q, want /tmp/report.md", got.SourcePath)
|
||||
}
|
||||
if got.BundlePath != "daily.md" {
|
||||
t.Fatalf("BundlePath = %q, want daily.md", got.BundlePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadRejectsMissingInputs(t *testing.T) {
|
||||
cfg := config.Defaults().Notify.Distributor
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(*Client, *UploadRequest)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "Token",
|
||||
mutate: func(c *Client, req *UploadRequest) {
|
||||
t.Setenv(c.TokenEnv, "")
|
||||
},
|
||||
wantErr: "token environment variable",
|
||||
},
|
||||
{
|
||||
name: "SourcePath",
|
||||
mutate: func(c *Client, req *UploadRequest) {
|
||||
req.SourcePath = ""
|
||||
},
|
||||
wantErr: "source path is required",
|
||||
},
|
||||
{
|
||||
name: "BundlePath",
|
||||
mutate: func(c *Client, req *UploadRequest) {
|
||||
req.BundlePath = ""
|
||||
},
|
||||
wantErr: "bundle path is required",
|
||||
},
|
||||
{
|
||||
name: "UploadClientFactory",
|
||||
mutate: func(c *Client, req *UploadRequest) {
|
||||
c.newUploadClient = nil
|
||||
},
|
||||
wantErr: "upload client factory is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
client := newClient(cfg, (&fakeUploadFactory{client: &fakeUploadClient{}}).newClient)
|
||||
req := validUploadRequest()
|
||||
tt.mutate(client, &req)
|
||||
|
||||
_, err := client.Upload(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatal("Upload() error = nil, want error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Fatalf("error = %q, want %q", err.Error(), tt.wantErr)
|
||||
}
|
||||
if strings.Contains(err.Error(), "secret-token") {
|
||||
t.Fatalf("error = %q, want no token value", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadWrapsFactoryErrorWithoutToken(t *testing.T) {
|
||||
cfg := config.Defaults().Notify.Distributor
|
||||
cfg.Endpoint = "https://distributor.example.test"
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
factory := &fakeUploadFactory{
|
||||
err: fmt.Errorf("factory failed with secret-token"),
|
||||
}
|
||||
client := newClient(cfg, factory.newClient)
|
||||
|
||||
_, err := client.Upload(context.Background(), validUploadRequest())
|
||||
if err == nil {
|
||||
t.Fatal("Upload() error = nil, want error")
|
||||
}
|
||||
if strings.Contains(err.Error(), "secret-token") {
|
||||
t.Fatalf("error = %q, want no token value", err.Error())
|
||||
}
|
||||
if !strings.Contains(err.Error(), cfg.Endpoint) {
|
||||
t.Fatalf("error = %q, want endpoint context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadWrapsUploadFailureWithContextWithoutToken(t *testing.T) {
|
||||
cfg := config.Defaults().Notify.Distributor
|
||||
cfg.Endpoint = "https://distributor.example.test"
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
factory := &fakeUploadFactory{
|
||||
client: &fakeUploadClient{err: fmt.Errorf("server rejected secret-token")},
|
||||
}
|
||||
client := newClient(cfg, factory.newClient)
|
||||
req := validUploadRequest()
|
||||
|
||||
_, err := client.Upload(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatal("Upload() error = nil, want error")
|
||||
}
|
||||
for _, want := range []string{cfg.Endpoint, req.BundleID, req.IdempotencyKey, req.SourcePath, req.BundlePath} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Fatalf("error = %q, want context %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(err.Error(), "secret-token") {
|
||||
t.Fatalf("error = %q, want no token value", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadPreservesIdempotencyConflictDiagnosis(t *testing.T) {
|
||||
cfg := config.Defaults().Notify.Distributor
|
||||
cfg.Endpoint = "https://distributor.example.test"
|
||||
t.Setenv(cfg.TokenEnv, "secret-token")
|
||||
factory := &fakeUploadFactory{
|
||||
client: &fakeUploadClient{
|
||||
err: &distributorupload.IdempotencyConflictError{
|
||||
HTTPError: distributorupload.HTTPError{
|
||||
StatusCode: 409,
|
||||
Status: "409 Conflict",
|
||||
Message: "conflicting upload for secret-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
client := newClient(cfg, factory.newClient)
|
||||
|
||||
_, err := client.Upload(context.Background(), validUploadRequest())
|
||||
if err == nil {
|
||||
t.Fatal("Upload() error = nil, want error")
|
||||
}
|
||||
var conflict *IdempotencyConflictError
|
||||
if !errors.As(err, &conflict) {
|
||||
t.Fatalf("Upload() error = %T %v, want IdempotencyConflictError", err, err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "idempotency conflict") {
|
||||
t.Fatalf("error = %q, want idempotency conflict diagnosis", err.Error())
|
||||
}
|
||||
if strings.Contains(err.Error(), "secret-token") {
|
||||
t.Fatalf("error = %q, want no token value", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func validUploadRequest() UploadRequest {
|
||||
return UploadRequest{
|
||||
BundleID: "weatherreporter.home.daily.run",
|
||||
IdempotencyKey: "weatherreporter.home.daily.run",
|
||||
SourcePath: "/tmp/report.md",
|
||||
BundlePath: "daily.md",
|
||||
}
|
||||
}
|
||||
|
||||
type fakeUploadFactory struct {
|
||||
endpoint string
|
||||
token string
|
||||
timeout time.Duration
|
||||
client *fakeUploadClient
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeUploadFactory) newClient(endpoint, token string, timeout time.Duration) (uploadClient, error) {
|
||||
f.endpoint = endpoint
|
||||
f.token = token
|
||||
f.timeout = timeout
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
return f.client, nil
|
||||
}
|
||||
|
||||
type fakeUploadClient struct {
|
||||
opts uploadFilesOptions
|
||||
result uploadFilesResult
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *fakeUploadClient) UploadFiles(ctx context.Context, opts uploadFilesOptions) (uploadFilesResult, error) {
|
||||
c.opts = opts
|
||||
if c.err != nil {
|
||||
return uploadFilesResult{}, c.err
|
||||
}
|
||||
return c.result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user