package zddc

import "testing"

// TestEmbeddedDefaults_LosslessUnderSerialization pins the invariant that
// makes it safe for policy.SerializableChain to drop PolicyChain.Embedded on
// the wire (and for policy.InternalDecider.Allow to reconstruct a chain from
// AllowInput without it).
//
// The decision path consults chain.Embedded at two sites:
//   - worm.go (WormZoneGrant): folds in a top-level chain.Embedded.Worm.
//   - roles.go (lookupRoleMembers / pathRoles): reads chain.Embedded.Roles.
//
// Today both are no-ops only because the baked-in baseline (EmbeddedDefaults,
// the root of every cascade — internal/zddc/defaults/.zddc) declares no
// top-level `worm:` and no role members (WORM is declared via the `paths:`
// tree, which lands in chain.Levels, not chain.Embedded). If a future default
// adds either, those contributions would be SILENTLY ignored by an external
// OPA (it never receives Embedded) and by the InternalDecider over a
// serialized chain — an authz divergence with no error. This test fails loudly
// at that moment so the change is paired with serializing PolicyChain.Embedded
// (one field on SerializableChain) before it ships.
func TestEmbeddedDefaults_LosslessUnderSerialization(t *testing.T) {
	e, err := EmbeddedDefaults()
	if err != nil {
		t.Fatalf("EmbeddedDefaults: %v", err)
	}

	if e.Worm != nil {
		t.Errorf("embedded baseline declares a top-level worm: %v — it is read at "+
			"decision time (WormZoneGrant) but dropped by policy.SerializableChain, "+
			"so external OPA and the reconstructed InternalDecider chain would "+
			"silently ignore it. Declare WORM via paths: (→ chain.Levels) or "+
			"serialize PolicyChain.Embedded.", e.Worm)
	}

	for name, role := range e.Roles {
		if len(role.Members) > 0 {
			t.Errorf("embedded baseline role %q has members %v — read at decision "+
				"time (chain.Embedded.Roles in roles.go) but dropped by "+
				"policy.SerializableChain. Keep embedded roles member-less or "+
				"serialize PolicyChain.Embedded.", name, role.Members)
		}
	}
}
