package mail import ( "context" "errors" "testing" ) // Idempotence is the contract, not a nicety: every walk re-runs after a // half-failure, so a second EnsureIdentity must be a success that returns // the SAME tokens — fresh tokens would orphan the DNS already published. func TestMemEnsureIdentityIsIdempotentAndTokenStable(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") first, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "mail-eu-west-1.x.com", true) if err != nil { t.Fatalf("EnsureIdentity: %v", err) } if len(first.DKIMTokens) != 3 { t.Fatalf("want 3 DKIM tokens, got %d", len(first.DKIMTokens)) } second, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "mail-eu-west-1.x.com", true) if err != nil { t.Fatalf("re-EnsureIdentity: %v", err) } if first.ARN != second.ARN || first.DKIMTokens[0] != second.DKIMTokens[0] { t.Fatal("re-ensuring changed the identity — the published DNS would be orphaned") } } // Without adopt, the fake refuses an existing identity untouched, as SES // does through awsProvisioner (#216). func TestMemEnsureIdentityWithoutAdoptRefusesUntouched(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") first, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "m1.x.com", false) if err != nil || !first.Created { t.Fatalf("create = %+v, %v", first, err) } if _, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "m2.x.com", false); !errors.Is(err, ErrIdentityExists) { t.Fatalf("re-ensure without adopt = %v, want ErrIdentityExists", err) } if got := p.Identities[identityKey("eu-west-1", "x.com")].MailFrom; got != "m1.x.com" { t.Fatalf("MAIL FROM rewritten to %q without adopt", got) } again, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "m1.x.com", true) if err != nil || again.Created { t.Fatalf("adopt = %+v, %v; want the existing identity, not Created", again, err) } } func TestMemIdentitiesAreRegionScoped(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") eu, _ := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "mail-eu-west-1.x.com", true) us, _ := p.EnsureIdentity(ctx, "us-east-1", "x.com", "mail-us-east-1.x.com", true) if eu.ARN == us.ARN { t.Fatal("one ARN served two regions") } if eu.DKIMTokens[0] == us.DKIMTokens[0] { t.Fatal("two regions shared a DKIM token — they are independent identities") } } func TestMemDeleteIsIdempotent(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") if err := p.DeleteIdentity(ctx, "eu-west-1", "absent.com"); err != nil { t.Fatalf("deleting an absent identity = %v, want nil", err) } if err := p.DeleteUser(ctx, "absent-user"); err != nil { t.Fatalf("deleting an absent user = %v, want nil", err) } } // The pause is SetKeyActive, and the fake must model it so a walk test can // assert a paused credential really is inactive. func TestMemSetKeyActiveTogglesTheKey(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") if _, err := p.EnsureUser(ctx, "u", UserPath, []string{"arn:aws:ses:eu-west-1:1:identity/x.com"}, nil, []string{"x.com"}); err != nil { t.Fatalf("EnsureUser: %v", err) } id, _, err := p.MintKey(ctx, "u") if err != nil { t.Fatalf("MintKey: %v", err) } if !p.Users["u"].Keys[id].Active { t.Fatal("a freshly minted key must be active") } if err := p.SetKeyActive(ctx, "u", id, false); err != nil { t.Fatalf("SetKeyActive: %v", err) } if p.Users["u"].Keys[id].Active { t.Fatal("SetKeyActive(false) did not deactivate the key") } } // ReapKeys is what finds a key stranded by a crash between mint and record — // no record names it, so only list-and-delete can. func TestMemReapKeysKeepsOnlyTheNamedOnes(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") p.EnsureUser(ctx, "u", UserPath, []string{"arn:aws:ses:eu-west-1:1:identity/x.com"}, nil, []string{"x.com"}) keep, _, _ := p.MintKey(ctx, "u") stranded, _, _ := p.MintKey(ctx, "u") if err := p.ReapKeys(ctx, "u", []string{keep}); err != nil { t.Fatalf("ReapKeys: %v", err) } if _, ok := p.Users["u"].Keys[stranded]; ok { t.Fatal("ReapKeys left a key the record does not name") } if _, ok := p.Users["u"].Keys[keep]; !ok { t.Fatal("ReapKeys deleted a key it was told to keep") } } // IAM hard-caps every user at two access keys. MintKey must enforce that // cap in the fake too, since a rotation walk (Task 13) mints a second key // while the first is still live, and a fake that let a third key through // would pass a test that fails the first time it runs against real IAM. // Both halves matter: the first two must succeed (a cap misapplied at two // would break ordinary rotation), and only the third must fail. func TestMemMintKeyEnforcesTheTwoKeyCap(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") if _, err := p.EnsureUser(ctx, "u", UserPath, []string{"arn:aws:ses:eu-west-1:1:identity/x.com"}, nil, []string{"x.com"}); err != nil { t.Fatalf("EnsureUser: %v", err) } if _, _, err := p.MintKey(ctx, "u"); err != nil { t.Fatalf("first MintKey: %v, want success", err) } if _, _, err := p.MintKey(ctx, "u"); err != nil { t.Fatalf("second MintKey: %v, want success — the cap must not fire before the third key", err) } if _, _, err := p.MintKey(ctx, "u"); err == nil { t.Fatal("third MintKey succeeded, want IAM's two-key-per-user limit enforced") } } // EnsureUser must refuse an ungranted principal rather than create one with // no policy — an unscoped sending user is the failure this package exists to // prevent. func TestMemEnsureUserRefusesAnEmptyGrant(t *testing.T) { if _, err := NewMemProvisioner("1").EnsureUser(context.Background(), "u", UserPath, nil, nil, nil); err == nil { t.Fatal("EnsureUser with no ARNs returned success") } } func TestMemFailNextIsOneShot(t *testing.T) { ctx, p := context.Background(), NewMemProvisioner("1") boom := errors.New("boom") p.FailNext["EnsureIdentity"] = boom if _, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "m.x.com", true); !errors.Is(err, boom) { t.Fatalf("first call = %v, want boom", err) } if _, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "m.x.com", true); err != nil { t.Fatalf("second call = %v, want nil", err) } } // The fake refuses to delete an identity that is still associated with a // tenant, because SES does. A walk that got the order wrong would pass // against a permissive fake and fail on the first real teardown. func TestMemProvisionerRefusesToDeleteAnAssociatedIdentity(t *testing.T) { ctx := context.Background() p := NewMemProvisioner("1") id, err := p.EnsureIdentity(ctx, "eu-west-1", "x.com", "mail-eu-west-1.x.com", true) if err != nil { t.Fatal(err) } if err := p.AssociateTenantResource(ctx, "carlos-bag", id.ARN); err != nil { t.Fatal(err) } if err := p.DeleteIdentity(ctx, "eu-west-1", "x.com"); err == nil { t.Fatal("an associated identity was deleted") } if err := p.DisassociateTenantResource(ctx, "carlos-bag", id.ARN); err != nil { t.Fatal(err) } // Disassociating twice is a success, like every other verb on this seam. if err := p.DisassociateTenantResource(ctx, "carlos-bag", id.ARN); err != nil { t.Fatal(err) } if err := p.DeleteIdentity(ctx, "eu-west-1", "x.com"); err != nil { t.Fatal(err) } }