package webingress import ( "bytes" "context" "encoding/json" "errors" "net/http" "testing" "time" ) func TestDefaultFabricBinderBuildsSignedEnvelopeAndSendsIt(t *testing.T) { signer := &recordingEnvelopeSigner{ signature: FabricEnvelopeSignature{KeyID: "node-key-1", Alg: "ed25519", Signature: "sig-1", SignedAt: "2026-05-17T00:00:02Z"}, } sender := &recordingEnvelopeSender{ response: FabricResponse{StatusCode: http.StatusAccepted, Body: []byte(`{"accepted":true}`)}, } binder := DefaultFabricBinder{Signer: signer, Sender: sender, Now: fixedEnvelopeNow} response, err := binder.Forward(context.Background(), FabricRequest{ SchemaVersion: "rap.web_ingress.fabric_request.v1", Method: "post", Path: "/platform-admin/root", Query: "tab=nodes", Host: "admin.example.test", ServiceType: "admin-ingress", Scope: "platform", ServiceClass: "platform_admin", Headers: http.Header{ "X-Trace-Id": []string{"trace-b", "trace-a"}, "Authorization": []string{"Bearer should-not-forward"}, "X-Empty-Header": []string{" "}, }, Body: []byte(`{"hello":"world"}`), ObservedAt: fixedNow(), }) if err != nil { t.Fatalf("Forward failed: %v", err) } if response.StatusCode != http.StatusAccepted { t.Fatalf("response = %+v", response) } if len(signer.canonical) == 0 { t.Fatal("signer did not receive canonical envelope") } if !bytes.Equal(sender.envelope.Canonical, signer.canonical) { t.Fatalf("sender canonical does not match signer canonical") } if sender.envelope.SchemaVersion != "rap.web_ingress.signed_fabric_service_channel_envelope.v1" { t.Fatalf("signed schema = %q", sender.envelope.SchemaVersion) } if sender.envelope.Signature.KeyID != "node-key-1" || sender.envelope.Signature.Signature != "sig-1" { t.Fatalf("signature = %+v", sender.envelope.Signature) } var canonical FabricServiceChannelEnvelope if err := json.Unmarshal(signer.canonical, &canonical); err != nil { t.Fatalf("decode canonical: %v", err) } if canonical.SchemaVersion != FabricServiceChannelEnvelopeSchema || canonical.RequestSchema != "rap.web_ingress.fabric_request.v1" || canonical.Method != http.MethodPost || canonical.Scope != "platform" || canonical.ServiceClass != "platform_admin" || canonical.BodyBase64 != "eyJoZWxsbyI6IndvcmxkIn0=" || canonical.ObservedAt != "2026-05-17T00:00:00Z" || canonical.EnvelopedAt != "2026-05-17T00:00:01Z" { t.Fatalf("canonical envelope = %+v", canonical) } if got := canonical.Headers["X-Trace-Id"]; len(got) != 2 || got[0] != "trace-a" || got[1] != "trace-b" { t.Fatalf("canonical headers = %#v", canonical.Headers) } if canonical.Headers["Authorization"] != nil || canonical.Headers["X-Empty-Header"] != nil { t.Fatalf("unsafe/empty headers leaked: %#v", canonical.Headers) } } func TestDefaultFabricBinderRequiresSignerAndSender(t *testing.T) { request := FabricRequest{Scope: "platform", ServiceClass: "platform_admin"} _, err := (DefaultFabricBinder{Sender: &recordingEnvelopeSender{}}).Forward(context.Background(), request) if !errors.Is(err, ErrFabricEnvelopeSignerRequired) { t.Fatalf("signer error = %v", err) } _, err = (DefaultFabricBinder{Signer: &recordingEnvelopeSigner{}}).Forward(context.Background(), request) if !errors.Is(err, ErrFabricEnvelopeSenderRequired) { t.Fatalf("sender error = %v", err) } } func TestDefaultFabricBinderRequiresScopeAndServiceClass(t *testing.T) { binder := DefaultFabricBinder{Signer: &recordingEnvelopeSigner{}, Sender: &recordingEnvelopeSender{}} _, err := binder.Forward(context.Background(), FabricRequest{ServiceClass: "platform_admin"}) if !errors.Is(err, ErrFabricEnvelopeScopeRequired) { t.Fatalf("scope error = %v", err) } _, err = binder.Forward(context.Background(), FabricRequest{Scope: "platform"}) if !errors.Is(err, ErrFabricEnvelopeClassRequired) { t.Fatalf("class error = %v", err) } } func TestDefaultFabricBinderPropagatesSignerAndSenderFailures(t *testing.T) { signerErr := errors.New("sign failed") senderErr := errors.New("send failed") request := FabricRequest{Scope: "platform", ServiceClass: "platform_admin"} _, err := (DefaultFabricBinder{ Signer: &recordingEnvelopeSigner{err: signerErr}, Sender: &recordingEnvelopeSender{}, }).Forward(context.Background(), request) if !errors.Is(err, signerErr) { t.Fatalf("signer error = %v", err) } _, err = (DefaultFabricBinder{ Signer: &recordingEnvelopeSigner{}, Sender: &recordingEnvelopeSender{err: senderErr}, }).Forward(context.Background(), request) if !errors.Is(err, senderErr) { t.Fatalf("sender error = %v", err) } } func fixedEnvelopeNow() time.Time { return time.Date(2026, 5, 17, 0, 0, 1, 0, time.UTC) } type recordingEnvelopeSigner struct { canonical []byte signature FabricEnvelopeSignature err error } func (s *recordingEnvelopeSigner) Sign(_ context.Context, canonical []byte) (FabricEnvelopeSignature, error) { s.canonical = append([]byte{}, canonical...) if s.err != nil { return FabricEnvelopeSignature{}, s.err } if s.signature.KeyID == "" { s.signature = FabricEnvelopeSignature{KeyID: "test-key", Alg: "ed25519", Signature: "test-signature"} } return s.signature, nil } type recordingEnvelopeSender struct { envelope SignedFabricServiceChannelEnvelope response FabricResponse err error } func (s *recordingEnvelopeSender) Send(_ context.Context, envelope SignedFabricServiceChannelEnvelope) (FabricResponse, error) { s.envelope = envelope if s.err != nil { return FabricResponse{}, s.err } return s.response, nil }