Files
iotlcm/worker-svr/internal/fullsync/contract/replay_test.go

104 lines
4.5 KiB
Go

package contract_test
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"time"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/contract"
)
func TestReplayRequestIdentity(t *testing.T) {
request := contract.ReplayRequest{SourceRunID: "source", IdempotencyKey: "key", RequestedBy: "operator"}
fingerprint, err := request.Fingerprint()
if err != nil || len(fingerprint) != 64 {
t.Fatalf("fingerprint=%q, err=%v", fingerprint, err)
}
for name, mutate := range map[string]func(*contract.ReplayRequest){
"same": func(*contract.ReplayRequest) {},
"key": func(r *contract.ReplayRequest) { r.IdempotencyKey = "another-key" },
"source": func(r *contract.ReplayRequest) { r.SourceRunID = "another-source" },
"body": func(r *contract.ReplayRequest) { r.RequestedBy = "another-operator" },
} {
copy := request
mutate(&copy)
got, err := copy.Fingerprint()
if err != nil || (got == fingerprint) != (name == "same" || name == "key") {
t.Errorf("%s fingerprint=%s err=%v", name, got, err)
}
}
if got := string(mustJSON(t, request)); got != `{"requestedBy":"operator"}` {
t.Fatalf("path/header leaked into body: %s", got)
}
for _, bad := range []contract.ReplayRequest{
{SourceRunID: "source", RequestedBy: "operator"},
{SourceRunID: "source", IdempotencyKey: "key", RequestedBy: " "},
{SourceRunID: "{source}", IdempotencyKey: "key", RequestedBy: "operator"},
{SourceRunID: "source", IdempotencyKey: "key\r\ninjected", RequestedBy: "operator"},
{SourceRunID: "source", IdempotencyKey: strings.Repeat("x", 257), RequestedBy: "operator"},
} {
if _, err := bad.Fingerprint(); !errors.Is(err, contract.ErrInvalidArgument) {
t.Errorf("bad request returned %v", err)
}
}
}
func TestActionProgressAndReceiptValidation(t *testing.T) {
for _, progress := range []contract.ReplayProgress{
{DiscoveredCount: -1}, {DiscoveredCount: 1, EnqueuedCount: 2},
{DiscoveredCount: 2, EnqueuedCount: 1, CleanedCount: 2}, {FailureCount: -1},
} {
if err := progress.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
t.Fatalf("bad progress %+v returned %v", progress, err)
}
}
now := time.Now().UTC()
action := contract.ReplayAction{ActionID: "action", SourceRunID: "source", TargetRunID: "target", RequestedBy: "operator", ReplayCycle: 1, Status: contract.ActionAccepted}
if err := action.Validate(); err != nil {
t.Fatal(err)
}
action.Status = contract.ActionCompleted
if err := action.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
t.Fatalf("completed action without receipt returned %v", err)
}
action.Receipt = &contract.ArchiveReceipt{Version: 1, Path: "receipt.json", Checksum: strings.Repeat("a", 64), ArchivedAt: now}
action.CompletedAt = &now
if err := action.Validate(); err != nil {
t.Fatal(err)
}
action.Receipt.Checksum = "not-a-checksum"
if err := action.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
t.Fatalf("bad receipt returned %v", err)
}
}
func TestActionWireFormatHidesInternalState(t *testing.T) {
action := contract.ReplayAction{
ActionID: "action", SourceRunID: "source", TargetRunID: "target", RequestedBy: "operator", ReplayCycle: 1,
Status: contract.ActionCleanupPending, IdempotencyKey: "secret", Fingerprint: "internal-fingerprint",
LeaseOwner: "worker", LeaseToken: "token", LeaseUntil: time.Now(), Revision: 9,
Progress: contract.ReplayProgress{ScanCursor: "opaque", DiscoveredCount: 10, EnqueuedCount: 8, CleanedCount: 7, FailureCount: 2},
}
data := mustJSON(t, action)
for _, hidden := range []string{"idempotency", "fingerprint", "lease", "revision", "secret", "token"} {
if strings.Contains(strings.ToLower(string(data)), hidden) {
t.Errorf("internal %s leaked: %s", hidden, data)
}
}
var decoded contract.ReplayAction
if err := json.Unmarshal(data, &decoded); err != nil || !reflect.DeepEqual(decoded.Progress, action.Progress) || decoded.Status != contract.ActionCleanupPending {
t.Fatalf("action roundtrip lost state: %+v, %v", decoded, err)
}
statusURL, err := contract.ActionStatusURL("action%1")
if err != nil || statusURL != "/internal/v1/full-sync-actions/action%251" {
t.Fatalf("status URL=%s, %v", statusURL, err)
}
accepted := contract.ReplayAccepted{ActionID: "action", TargetRunID: "target", Status: contract.ActionAccepted, StatusURL: "/internal/v1/full-sync-actions/action"}
if got := string(mustJSON(t, accepted)); got != `{"actionId":"action","targetRunId":"target","status":"accepted","statusUrl":"/internal/v1/full-sync-actions/action"}` {
t.Fatalf("202 contract changed: %s", got)
}
}