447 lines
16 KiB
Go
447 lines
16 KiB
Go
package output
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type forgedResult struct {
|
|
env Envelope
|
|
exit int
|
|
}
|
|
|
|
func (r forgedResult) Outcome() Outcome { return r.env.Outcome }
|
|
func (r forgedResult) ExitCode() int { return r.exit }
|
|
func (r forgedResult) envelope() *Envelope { copy := r.env; return © }
|
|
|
|
type cloneNode struct {
|
|
Name string
|
|
Next *cloneNode
|
|
Values []string
|
|
Hidden *string
|
|
}
|
|
|
|
func TestFrameworkResultCloneAndValidationEdges(t *testing.T) {
|
|
if cloneReflectValue(reflect.Value{}, nil).IsValid() {
|
|
t.Fatal("invalid reflect value became valid")
|
|
}
|
|
var nilAny any
|
|
if got := cloneResultData(nilAny); got != nil {
|
|
t.Fatalf("cloneResultData(nil)=%v", got)
|
|
}
|
|
|
|
node := &cloneNode{Name: "before", Values: []string{"one"}}
|
|
node.Next = node
|
|
payload := map[string]any{
|
|
"node": node,
|
|
"nil_ptr": (*cloneNode)(nil),
|
|
"nil_map": map[string]string(nil),
|
|
"nil_slice": []string(nil),
|
|
"array": [2]string{"a", "b"},
|
|
"interface": any([]string{"x"}),
|
|
}
|
|
cloned := cloneResultData(payload).(map[string]any)
|
|
gotNode := cloned["node"].(*cloneNode)
|
|
if gotNode == node || gotNode.Next != gotNode || gotNode.Values[0] != "one" {
|
|
t.Fatalf("cycle-safe clone=%#v", gotNode)
|
|
}
|
|
node.Name, node.Values[0] = "after", "two"
|
|
if gotNode.Name != "before" || gotNode.Values[0] != "one" {
|
|
t.Fatalf("clone was mutated: %#v", gotNode)
|
|
}
|
|
|
|
started := true
|
|
retry := int64(3)
|
|
info := &ErrorInfo{
|
|
Type: "api", UpstreamCode: map[string]any{"token": "kept"},
|
|
Params: []string{"p"}, Actions: []string{"a"}, AvailableFlags: []string{"f"},
|
|
ExecutionStarted: &started, RetryAfterSeconds: &retry,
|
|
Details: map[string]any{"nested": []any{"x"}}, RPCData: []any{"rpc"},
|
|
}
|
|
copyInfo := cloneErrorInfo(info)
|
|
if copyInfo == info || copyInfo.ExecutionStarted == info.ExecutionStarted || copyInfo.RetryAfterSeconds == info.RetryAfterSeconds {
|
|
t.Fatal("ErrorInfo pointer fields were not detached")
|
|
}
|
|
if cloneErrorInfo(nil) != nil {
|
|
t.Fatal("cloneErrorInfo(nil) must be nil")
|
|
}
|
|
cyclicMap := map[string]any{}
|
|
cyclicMap["self"] = cyclicMap
|
|
clonedMap := cloneResultData(cyclicMap).(map[string]any)
|
|
if reflect.ValueOf(clonedMap).Pointer() == reflect.ValueOf(cyclicMap).Pointer() {
|
|
t.Fatal("cyclic map was not cloned")
|
|
}
|
|
cyclicSlice := make([]any, 1)
|
|
cyclicSlice[0] = cyclicSlice
|
|
clonedSlice := cloneResultData(cyclicSlice).([]any)
|
|
if reflect.ValueOf(clonedSlice).Pointer() == reflect.ValueOf(cyclicSlice).Pointer() {
|
|
t.Fatal("cyclic slice was not cloned")
|
|
}
|
|
|
|
validFailure := Failure(&ErrorInfo{Type: "api", Message: "failed"})
|
|
if err := ValidateResult(validFailure); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validPending := Pending(nil, &OperationInfo{ID: "task", State: "processing", NextCommand: "dws task get"})
|
|
if err := ValidateResult(validPending); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decorated := Success(nil, WithIdentity("user"), WithDryRun(), ResultOption{})
|
|
if decorated.Outcome() != OutcomeSuccess {
|
|
t.Fatalf("Outcome=%s", decorated.Outcome())
|
|
}
|
|
decoratedEnv, err := EnvelopeFromResult(decorated)
|
|
if err != nil || decoratedEnv.Identity != "user" || !decoratedEnv.DryRun {
|
|
t.Fatalf("decorated=%+v err=%v", decoratedEnv, err)
|
|
}
|
|
|
|
badPending := []CommandResult{
|
|
Pending(nil, nil),
|
|
Pending(nil, &OperationInfo{State: "processing", NextCommand: "next"}),
|
|
Pending(nil, &OperationInfo{ID: "id", NextCommand: "next"}),
|
|
Pending(nil, &OperationInfo{ID: "id", State: "processing"}),
|
|
}
|
|
for _, result := range badPending {
|
|
if ValidateResult(result) == nil {
|
|
t.Fatalf("malformed pending accepted: %#v", result)
|
|
}
|
|
}
|
|
if ValidateResult(Partial(nil)) == nil {
|
|
t.Fatal("nil partial accepted")
|
|
}
|
|
if err := StoreResult(context.Background(), Pending(nil, nil)); err == nil {
|
|
t.Fatal("StoreResult accepted malformed result")
|
|
}
|
|
if ValidateResult(forgedResult{env: Envelope{OK: true, Outcome: OutcomeSuccess}, exit: 9}) == nil {
|
|
t.Fatal("forged exit code accepted")
|
|
}
|
|
badOverride := &commandResult{env: Envelope{OK: false, Outcome: OutcomeFailure, Error: &ErrorInfo{Type: "api"}}, exitCodeOverride: true}
|
|
if ValidateResult(badOverride) == nil {
|
|
t.Fatal("non-positive override accepted")
|
|
}
|
|
badWire := &commandResult{env: Envelope{OK: false, Outcome: OutcomeFailure, Error: &ErrorInfo{Type: "api", ExitCode: 5}}, exitCode: 1}
|
|
if ValidateResult(badWire) == nil {
|
|
t.Fatal("wire/process exit mismatch accepted")
|
|
}
|
|
}
|
|
|
|
type redactionEdge struct {
|
|
Password *string `json:"password"`
|
|
Plain string
|
|
Ignored string `json:"-"`
|
|
hidden string
|
|
}
|
|
|
|
type redactCycle struct{ Next *redactCycle }
|
|
|
|
func TestFrameworkRedactionReflectionEdges(t *testing.T) {
|
|
if redactEnvelope(nil) != nil {
|
|
t.Fatal("redactEnvelope(nil) must be nil")
|
|
}
|
|
seen := map[redactVisit]struct{}{}
|
|
redactReflectValue(reflect.Value{}, "", seen)
|
|
|
|
secret := "pointer-secret"
|
|
fixture := &redactionEdge{Password: &secret, Plain: "token=plain-secret", Ignored: "ignored-secret", hidden: "hidden-secret"}
|
|
redactReflectValue(reflect.ValueOf(fixture), "", seen) // not settable
|
|
redactReflectValue(reflect.ValueOf(&fixture).Elem(), "", map[redactVisit]struct{}{})
|
|
if fixture.Password == nil || *fixture.Password != redactedValue || !strings.Contains(fixture.Plain, redactedValue) {
|
|
t.Fatalf("fixture not redacted: %+v", fixture)
|
|
}
|
|
if fixture.Ignored != "ignored-secret" || fixture.hidden != "hidden-secret" {
|
|
t.Fatalf("ignored fields changed: %+v", fixture)
|
|
}
|
|
|
|
array := [2]string{"password=array-one", "token=array-two"}
|
|
redactReflectValue(reflect.ValueOf(&array).Elem(), "", map[redactVisit]struct{}{})
|
|
if strings.Contains(strings.Join(array[:], ","), "array-") {
|
|
t.Fatalf("array not redacted: %#v", array)
|
|
}
|
|
|
|
cyclicMap := map[string]any{}
|
|
cyclicMap["self"] = cyclicMap
|
|
cyclicMap["password"] = "map-secret"
|
|
redactReflectValue(reflect.ValueOf(cyclicMap), "", map[redactVisit]struct{}{})
|
|
if cyclicMap["password"] != redactedValue {
|
|
t.Fatalf("map secret=%v", cyclicMap["password"])
|
|
}
|
|
cyclicSlice := make([]any, 2)
|
|
cyclicSlice[0] = cyclicSlice
|
|
cyclicSlice[1] = "password=slice-secret"
|
|
redactReflectValue(reflect.ValueOf(cyclicSlice), "", map[redactVisit]struct{}{})
|
|
pointerCycle := &redactCycle{}
|
|
pointerCycle.Next = pointerCycle
|
|
redactReflectValue(reflect.ValueOf(pointerCycle), "", map[redactVisit]struct{}{})
|
|
|
|
unsettable := reflect.ValueOf("secret")
|
|
if setRedactedValue(unsettable) {
|
|
t.Fatal("unsettable value was redacted")
|
|
}
|
|
settableString := reflect.New(reflect.TypeOf("")).Elem()
|
|
if !setRedactedValue(settableString) || settableString.String() != redactedValue {
|
|
t.Fatal("settable string was not redacted")
|
|
}
|
|
settablePointer := reflect.New(reflect.TypeOf((*string)(nil))).Elem()
|
|
if !setRedactedValue(settablePointer) || settablePointer.Elem().String() != redactedValue {
|
|
t.Fatal("settable pointer was not redacted")
|
|
}
|
|
settableInt := reflect.New(reflect.TypeOf(0)).Elem()
|
|
if setRedactedValue(settableInt) {
|
|
t.Fatal("integer unexpectedly redacted")
|
|
}
|
|
|
|
text := redactRecognizableSecrets("?=unchanged https://u:p@example.test?normal=ok&signature=s")
|
|
if !strings.Contains(text, "?=unchanged") || strings.Contains(text, "u:p") || strings.Contains(text, "signature=s") {
|
|
t.Fatalf("recognized secret redaction=%q", text)
|
|
}
|
|
}
|
|
|
|
func TestFrameworkEnvelopeNilAndNestedValidationEdges(t *testing.T) {
|
|
if (&Pagination{}).Validate() == nil || (*Pagination)(nil).Validate() != nil {
|
|
t.Fatal("pagination nil/two-state validation mismatch")
|
|
}
|
|
if (*ErrorInfo)(nil).Validate() == nil {
|
|
t.Fatal("nil ErrorInfo accepted")
|
|
}
|
|
negative := int64(-1)
|
|
for _, info := range []*ErrorInfo{
|
|
{Type: "unknown"},
|
|
{Type: "api", ExitCode: -1},
|
|
{Type: "api", RetryAfterSeconds: &negative},
|
|
} {
|
|
if info.Validate() == nil {
|
|
t.Fatalf("invalid ErrorInfo accepted: %+v", info)
|
|
}
|
|
}
|
|
if (*PartialData)(nil).Validate() != nil {
|
|
t.Fatal("nil PartialData should be compatible")
|
|
}
|
|
if (*OperationInfo)(nil).ValidateNextCommand() != nil || (*OperationInfo)(nil).ValidateTimeoutState() != nil || (*OperationInfo)(nil).Validate() != nil {
|
|
t.Fatal("nil operation should validate")
|
|
}
|
|
env := NewSuccessEnvelope(nil)
|
|
env.Meta = &Meta{Operation: &OperationInfo{NextCommand: " bad "}, Pagination: &Pagination{}}
|
|
if env.Validate() == nil {
|
|
t.Fatal("nested invalid metadata accepted")
|
|
}
|
|
}
|
|
|
|
func TestFrameworkResultStoreLifecycleEdges(t *testing.T) {
|
|
if _, ok := resultStoreFromContext(nil); ok {
|
|
t.Fatal("nil context had a store")
|
|
}
|
|
if err := ResetResultStore(context.Background()); err == nil {
|
|
t.Fatal("ResetResultStore without root store succeeded")
|
|
}
|
|
ctx, store := WithResultStore(nil)
|
|
ctx2, same := WithResultStore(ctx)
|
|
if ctx2 != ctx || same != store {
|
|
t.Fatal("WithResultStore did not reuse existing store")
|
|
}
|
|
if err := StoreResult(context.Background(), Success(nil)); err == nil {
|
|
t.Fatal("StoreResult without root store succeeded")
|
|
}
|
|
if err := StoreResult(ctx, Success(map[string]any{"id": "x"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := StoreResult(ctx, Success(nil)); err == nil {
|
|
t.Fatal("duplicate result accepted")
|
|
}
|
|
if _, _, err := EmitStoredResult(nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plain := &cobra.Command{Use: "plain"}
|
|
if _, _, err := EmitStoredResult(plain); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plain.SetContext(ctx)
|
|
if _, _, err := EmitStoredResult(plain); err == nil {
|
|
t.Fatal("legacy command emitted unified result")
|
|
}
|
|
|
|
active := &cobra.Command{Use: "active"}
|
|
active.SetContext(ctx)
|
|
SetCommandRollout(active, RolloutUnifiedActive)
|
|
var out bytes.Buffer
|
|
active.SetOut(&out)
|
|
code, emitted, err := EmitStoredResult(active)
|
|
if err != nil || code != 0 || !emitted {
|
|
t.Fatalf("EmitStoredResult=(%d,%v,%v)", code, emitted, err)
|
|
}
|
|
code, emitted, err = EmitStoredResult(active)
|
|
if err != nil || code != 0 || !emitted {
|
|
t.Fatalf("second EmitStoredResult=(%d,%v,%v)", code, emitted, err)
|
|
}
|
|
if code, attempted, emitted, risk := StoredEmissionState(store); code != 0 || !attempted || !emitted || !risk {
|
|
t.Fatalf("state=(%d,%v,%v,%v)", code, attempted, emitted, risk)
|
|
}
|
|
if code, ok := StoredExitCode(store); code != 0 || !ok {
|
|
t.Fatalf("StoredExitCode=(%d,%v)", code, ok)
|
|
}
|
|
if err := ResetResultStore(ctx); err != nil {
|
|
t.Fatalf("ResetResultStore: %v", err)
|
|
}
|
|
if code, attempted, emitted, risk := StoredEmissionState(store); code != 0 || attempted || emitted || risk {
|
|
t.Fatalf("reset state=(%d,%v,%v,%v)", code, attempted, emitted, risk)
|
|
}
|
|
if _, ok := StoredExitCode(store); ok {
|
|
t.Fatal("reset store retained an emitted exit code")
|
|
}
|
|
if err := StoreResult(ctx, Success(map[string]any{"id": "after-reset"})); err != nil {
|
|
t.Fatalf("StoreResult after reset: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFrameworkResultStoreNilStateAndWriteFailure(t *testing.T) {
|
|
code, attempted, emitted, risk := StoredEmissionState(nil)
|
|
if code != 0 || attempted || emitted || risk {
|
|
t.Fatalf("nil state=(%d,%v,%v,%v)", code, attempted, emitted, risk)
|
|
}
|
|
if code, ok := StoredExitCode(nil); code != 0 || ok {
|
|
t.Fatalf("nil exit=(%d,%v)", code, ok)
|
|
}
|
|
ctx, store := WithResultStore(context.Background())
|
|
if err := StoreResult(ctx, Success(nil)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmd := &cobra.Command{Use: "active"}
|
|
cmd.SetContext(ctx)
|
|
SetCommandRollout(cmd, RolloutUnifiedActive)
|
|
cmd.SetOut(errorWriter{})
|
|
if code, emitted, err := EmitStoredResult(cmd); code != exitCodeInternal || emitted || err == nil {
|
|
t.Fatalf("write failure=(%d,%v,%v)", code, emitted, err)
|
|
}
|
|
if code, attempted, emitted, risk := StoredEmissionState(store); code != exitCodeInternal || !attempted || emitted || risk {
|
|
t.Fatalf("failed state=(%d,%v,%v,%v)", code, attempted, emitted, risk)
|
|
}
|
|
if _, err := EnvelopeFromResult(nil); err == nil {
|
|
t.Fatal("EnvelopeFromResult(nil) succeeded")
|
|
}
|
|
}
|
|
|
|
type errorWriter struct{}
|
|
|
|
func (errorWriter) Write([]byte) (int, error) { return 0, errors.New("write failed") }
|
|
|
|
func TestFrameworkEmitterAndRolloutEdges(t *testing.T) {
|
|
if jsonShorthandActive(nil) {
|
|
t.Fatal("nil command activated --json")
|
|
}
|
|
if err := WriteEnvelope(nil, nil, FormatJSON); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmd := &cobra.Command{Use: "cmd"}
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(errorWriter{})
|
|
cmd.Flags().String("format", "unknown", "")
|
|
if err := WriteEnvelope(cmd, NewSuccessEnvelope(nil), FormatJSON); err == nil {
|
|
t.Fatal("warning write error was ignored")
|
|
}
|
|
if _, _, err := emitResult(nil, nil); err == nil {
|
|
t.Fatal("nil result accepted")
|
|
}
|
|
broken := NewSuccessEnvelope(make(chan int))
|
|
if err := WriteEnvelope(&cobra.Command{Use: "broken"}, broken, FormatJSON); err == nil {
|
|
t.Fatal("WriteEnvelope accepted unrenderable data")
|
|
}
|
|
if _, _, err := emitResult(&cobra.Command{Use: "broken"}, Success(make(chan int))); err == nil {
|
|
t.Fatal("emitResult accepted unrenderable data")
|
|
}
|
|
|
|
human := &cobra.Command{Use: "human"}
|
|
human.Flags().String("format", "table", "")
|
|
human.SetErr(errorWriter{})
|
|
if _, risk, err := emitResult(human, Failure(&ErrorInfo{Type: "api", Message: "boom"})); err == nil || risk {
|
|
t.Fatalf("human failure write=(risk=%v, err=%v)", risk, err)
|
|
}
|
|
short := &shortWriter{}
|
|
human.SetErr(short)
|
|
if _, risk, err := emitResult(human, Failure(&ErrorInfo{Type: "api"})); !errors.Is(err, io.ErrShortWrite) || !risk {
|
|
t.Fatalf("human short write=(risk=%v, err=%v)", risk, err)
|
|
}
|
|
|
|
machine := &cobra.Command{Use: "machine"}
|
|
machine.SetOut(errorWriter{})
|
|
if _, risk, err := emitResult(machine, Success(nil)); err == nil || risk {
|
|
t.Fatalf("machine write=(risk=%v, err=%v)", risk, err)
|
|
}
|
|
machine.SetOut(&shortWriter{})
|
|
if _, risk, err := emitResult(machine, Success(nil)); !errors.Is(err, io.ErrShortWrite) || !risk {
|
|
t.Fatalf("machine short write=(risk=%v, err=%v)", risk, err)
|
|
}
|
|
|
|
if err := renderEnvelope(io.Discard, nil, nil, FormatJSON, "", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var fallback bytes.Buffer
|
|
if err := renderEnvelopeInto(&fallback, io.Discard, NewSuccessEnvelope(map[string]any{"id": "x"}), Format("unexpected"), "", ""); err != nil || !strings.Contains(fallback.String(), `"outcome"`) {
|
|
t.Fatalf("unknown format fallback=%q err=%v", fallback.String(), err)
|
|
}
|
|
if got := ExitCodeForEnvelope(&Envelope{Outcome: OutcomeFailure, Error: &ErrorInfo{Type: "permission"}}); got != exitCodePermission {
|
|
t.Fatalf("permission exit=%d", got)
|
|
}
|
|
var ndjson bytes.Buffer
|
|
if err := renderEnvelopeInto(&ndjson, io.Discard, Failure(&ErrorInfo{Type: "api"}).envelope(), FormatNDJSON, "", ""); err != nil || strings.Count(strings.TrimSpace(ndjson.String()), "\n") != 0 {
|
|
t.Fatalf("NDJSON failure=%q err=%v", ndjson.String(), err)
|
|
}
|
|
if _, err := writeAllCount(&shortWriter{}, []byte("payload")); !errors.Is(err, io.ErrShortWrite) {
|
|
t.Fatalf("short write err=%v", err)
|
|
}
|
|
|
|
if _, err := ParseRolloutState("bad"); err == nil {
|
|
t.Fatal("invalid rollout parsed")
|
|
}
|
|
if err := ValidateRolloutTransition("bad", RolloutLegacyOnly, false); err == nil {
|
|
t.Fatal("invalid from state accepted")
|
|
}
|
|
if err := ValidateRolloutTransition(RolloutLegacyOnly, "bad", false); err == nil {
|
|
t.Fatal("invalid to state accepted")
|
|
}
|
|
if rolloutRank("bad") != -1 {
|
|
t.Fatal("unknown rollout rank")
|
|
}
|
|
SetCommandRollout(nil, RolloutUnifiedActive)
|
|
if err := ValidateUnifiedFormat(nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Fatal("invalid rollout did not panic")
|
|
}
|
|
}()
|
|
SetCommandRollout(&cobra.Command{Use: "bad"}, "bad")
|
|
}
|
|
|
|
func TestFrameworkMCPAdapterErrors(t *testing.T) {
|
|
if _, err := AdaptMCP(nil); err == nil {
|
|
t.Fatal("AdaptMCP(nil) succeeded")
|
|
}
|
|
originalMarshal, originalUnmarshal := marshalMCPResult, unmarshalMCPResult
|
|
t.Cleanup(func() { marshalMCPResult, unmarshalMCPResult = originalMarshal, originalUnmarshal })
|
|
marshalMCPResult = func(any) ([]byte, error) { return nil, errors.New("marshal") }
|
|
if _, err := AdaptMCP(Success(nil)); err == nil {
|
|
t.Fatal("MCP marshal failure ignored")
|
|
}
|
|
marshalMCPResult = func(any) ([]byte, error) { return []byte("{"), nil }
|
|
unmarshalMCPResult = originalUnmarshal
|
|
if _, err := AdaptMCP(Success(nil)); err == nil {
|
|
t.Fatal("MCP decode failure ignored")
|
|
}
|
|
}
|
|
|
|
type shortWriter struct{}
|
|
|
|
func (*shortWriter) Write(p []byte) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
return len(p) - 1, nil
|
|
}
|