package clusterauth import ( "encoding/json" "errors" "fmt" "testing" "time" ) func TestSignAndVerifyRawPayload(t *testing.T) { keys, err := GenerateKeyPair() if err != nil { t.Fatalf("GenerateKeyPair: %v", err) } payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":1}`) signature, err := SignRaw(keys.PrivateKeyB64, payload, time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)) if err != nil { t.Fatalf("SignRaw: %v", err) } if signature.KeyFingerprint != keys.Fingerprint { t.Fatalf("fingerprint = %q, want %q", signature.KeyFingerprint, keys.Fingerprint) } if err := VerifyRaw(keys.PublicKeyB64, payload, signature); err != nil { t.Fatalf("VerifyRaw: %v", err) } } func TestVerifyRawRejectsTamperedPayload(t *testing.T) { keys, err := GenerateKeyPair() if err != nil { t.Fatalf("GenerateKeyPair: %v", err) } payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":1}`) signature, err := SignRaw(keys.PrivateKeyB64, payload, time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)) if err != nil { t.Fatalf("SignRaw: %v", err) } tampered := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":2}`) if err := VerifyRaw(keys.PublicKeyB64, tampered, signature); !errors.Is(err, ErrInvalidSignature) { t.Fatalf("err = %v, want ErrInvalidSignature", err) } } func TestVerifyQuorumRawAcceptsThreshold(t *testing.T) { payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"rap.node_update_plan_authority.v1","action":"update"}`) descriptor, keys := testQuorum(t, 3, 2) payloadHash, err := HashRaw(payload) if err != nil { t.Fatalf("HashRaw: %v", err) } quorumHash, err := QuorumDescriptorHash(descriptor) if err != nil { t.Fatalf("QuorumDescriptorHash: %v", err) } signatureA, err := SignRaw(keys[0].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC)) if err != nil { t.Fatalf("SignRaw A: %v", err) } signatureB, err := SignRaw(keys[1].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 1, 0, time.UTC)) if err != nil { t.Fatalf("SignRaw B: %v", err) } envelope := QuorumEnvelope{ SchemaVersion: QuorumEnvelopeVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: 2, PayloadSHA256: payloadHash, QuorumSHA256: quorumHash, Signatures: []Signature{signatureA, signatureB}, } if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); err != nil { t.Fatalf("VerifyQuorumRaw: %v", err) } } func TestVerifyQuorumRawRejectsBelowThreshold(t *testing.T) { payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"rap.node_update_plan_authority.v1","action":"update"}`) descriptor, keys := testQuorum(t, 3, 2) payloadHash, _ := HashRaw(payload) quorumHash, _ := QuorumDescriptorHash(descriptor) signature, err := SignRaw(keys[0].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC)) if err != nil { t.Fatalf("SignRaw: %v", err) } envelope := QuorumEnvelope{ SchemaVersion: QuorumEnvelopeVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: 2, PayloadSHA256: payloadHash, QuorumSHA256: quorumHash, Signatures: []Signature{signature}, } if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); !errors.Is(err, ErrInvalidSignature) { t.Fatalf("err = %v, want ErrInvalidSignature", err) } } func testQuorum(t *testing.T, count int, threshold int) (QuorumDescriptor, []KeyPair) { t.Helper() descriptor := QuorumDescriptor{ SchemaVersion: QuorumSchemaVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: threshold, } keys := make([]KeyPair, 0, count) for i := 0; i < count; i++ { keyPair, err := GenerateKeyPair() if err != nil { t.Fatalf("GenerateKeyPair: %v", err) } descriptor.Members = append(descriptor.Members, QuorumMember{ NodeID: fmt.Sprintf("authority-%d", i+1), Role: "update-authority", PublicKey: keyPair.PublicKeyB64, PublicKeyFingerprint: keyPair.Fingerprint, Scopes: []string{"update-authority"}, }) keys = append(keys, keyPair) } return descriptor, keys }