Files

164 lines
5.5 KiB
Go

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
}