Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/platform/orgsettings/settings_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/platform/orgsettings/settings_test.go |
| Original SHA-256 | 67b36c75520c89307c25e9dc89dad4617e2220b714c040a224abd39475ff57b3 |
| Git blob SHA | 7cf6e3b1a54fd9f08414d65b9c819b9b87d2ff2c |
| Reading-copy SHA-256 | 67b36c75520c89307c25e9dc89dad4617e2220b714c040a224abd39475ff57b3 |
Recovered source transcript
Download complete source text ↓262 lines · 10.8 KB
package orgsettings
import (
"testing"
"github.com/google/uuid"
"github.com/missionworkforce/missionmatch/internal/platform/security"
)
// Defaults must be SAFE values, not zero values, and this is the test that says why. A Settings built from
// the zero value would have an empty WorkVisibility (denies, which is at least safe) but also a zero
// ClaimExpiryHours, which expires every claim the instant it is made, and a zero sensitivity on every field.
// Defaults is the answer a customer would recognise.
func TestDefaultsAreSafeNotZero(t *testing.T) {
d := Defaults()
if d.RateVisibility != security.SensitivityManagers {
t.Errorf("rate visibility default is %q, pay data belongs at the management tier", d.RateVisibility)
}
if d.MarginVisibility != security.SensitivityOwner {
t.Errorf("margin visibility default is %q, margin is the tightest field there is", d.MarginVisibility)
}
if d.ContactVisibility != security.SensitivityTeam {
t.Errorf("contact visibility default is %q", d.ContactVisibility)
}
if d.WorkVisibility != security.VisibilityOrganization {
t.Errorf("work visibility default is %q, the double work control must ship ON", d.WorkVisibility)
}
if d.EmailDeliveryEnabled {
t.Error("email delivery must default OFF so the pilot behaviour is preserved")
}
if d.ClaimExpiryHours <= 0 {
t.Error("a zero claim expiry would expire every claim the moment it was made")
}
if d.InviteExpiryHours <= 0 {
t.Error("a zero invite expiry would expire every invitation immediately")
}
if d.SMTPSecurity != "starttls" {
t.Errorf("smtp security default is %q, it must never default to cleartext", d.SMTPSecurity)
}
// Every default sensitivity must be one this system recognises. An unrecognised one denies, so a typo
// here would silently hide a field from everybody including the owner.
for name, s := range map[string]security.Sensitivity{
"rate": d.RateVisibility, "margin": d.MarginVisibility, "contact": d.ContactVisibility,
} {
if !security.ValidSensitivity(s) {
t.Errorf("%s default %q is not a recognised sensitivity", name, s)
}
}
if !security.ValidWorkVisibility(d.WorkVisibility) {
t.Errorf("work visibility default %q is not recognised", d.WorkVisibility)
}
// THERE IS NO TIME ZONE HERE ANY MORE, and this asserts it stays that way. organizations.time_zone has
// held the working day since migration 0057 and already decides what "today" means for the task queue.
// A second copy on org_settings diverged the moment somebody edited whichever one their screen showed.
}
// THE REGRESSION. Save was one INSERT ... ON CONFLICT DO UPDATE, which silently applied nothing when the
// organization had no settings row: the INSERT branch ran and the SET clause never did. provision_org does
// not create a settings row, so every newly provisioned customer's FIRST save reported success and changed
// nothing. This asserts the shape that made it possible is gone.
//
// It reads the source rather than hitting a database because the defect was in the SQL text itself, and a
// test that needs Postgres is a test that does not run in CI.
func TestSaveDoesNotRelyOnOnConflictToApplyValues(t *testing.T) {
src := readSource(t, "settings.go")
if containsAll(src, "INSERT INTO org_settings", "ON CONFLICT (organization_id) DO UPDATE SET") {
t.Error("Save is back to a single upsert; an organization with no row will silently ignore its first save")
}
if !containsAll(src, "ON CONFLICT (organization_id) DO NOTHING") {
t.Error("ensureRow is missing, writers can no longer guarantee the row exists")
}
// Every writer must guarantee the row first, or it is an UPDATE against nothing that reports success.
for _, fn := range []string{"func (s *Store) Save(", "func (s *Store) MarkVerified(", "func (s *Store) MarkAttemptFailed("} {
body := functionBody(src, fn)
if body == "" {
t.Fatalf("could not find %s", fn)
}
if !containsAll(body, "s.ensureRow(ctx, tx)") {
t.Errorf("%s does not call ensureRow, an UPDATE against a missing row affects zero rows and reports success", fn)
}
}
}
// A credential must never survive into a stored or returned error string.
func TestRedactCredentials(t *testing.T) {
in := "535 auth failed for user svc-mailer with password hunter2000seven"
out := redactCredentials(in, "svc-mailer", "hunter2000seven")
for _, secret := range []string{"svc-mailer", "hunter2000seven"} {
if contains(out, secret) {
t.Errorf("%q survived redaction: %q", secret, out)
}
}
// A short value is deliberately NOT redacted: replacing every two character run would destroy the
// message and tell the reader nothing.
if got := redactCredentials("no such host abc", "ab", "cd"); got != "no such host abc" {
t.Errorf("a short value should be left alone, got %q", got)
}
}
func TestPlausibleAddress(t *testing.T) {
for _, good := range []string{"a@b.co", "talent@agency.example.com", "first.last+tag@sub.domain.org"} {
if !plausibleAddress(good) {
t.Errorf("%q is a valid address and was rejected", good)
}
}
for _, bad := range []string{"", "nobody", "no at sign.com", "a@b", "a b@c.com", "x@y.com, z@y.com", "<a@b.co>"} {
if plausibleAddress(bad) {
t.Errorf("%q is not a usable address and was accepted", bad)
}
}
}
func TestValidateRejectsWhatTheDatabaseWouldReject(t *testing.T) {
s := func(v string) *string { return &v }
i := func(v int) *int { return &v }
cases := []struct {
name string
u Update
field string
}{
{"cleartext mail", Update{SMTPSecurity: s("none")}, "smtp_security"},
{"impossible port", Update{SMTPPort: i(70000)}, "smtp_port"},
{"unknown sensitivity", Update{RateVisibility: s("everyone")}, "rate_visibility"},
{"unknown visibility", Update{WorkVisibility: s("public")}, "work_visibility"},
{"owner as default invite", Update{DefaultInviteRole: s("owner")}, "default_invite_role"},
{"bad sender", Update{EmailFromAddress: s("not an address")}, "email_from_address"},
{"claim expiry too long", Update{ClaimExpiryHours: i(99999)}, "claim_expiry_hours"},
{"invite expiry too long", Update{InviteExpiryHours: i(9999)}, "invite_expiry_hours"},
}
for _, c := range cases {
field, err := c.u.Validate()
if err == nil {
t.Errorf("%s should be refused and was accepted", c.name)
continue
}
if field != c.field {
t.Errorf("%s reported field %q, expected %q", c.name, field, c.field)
}
}
if _, err := (Update{SMTPSecurity: s("starttls")}).Validate(); err != nil {
t.Errorf("a valid patch was refused: %v", err)
}
}
// Changing anything a previous successful test send proved must clear that proof, or the platform goes on
// claiming mail works against a configuration nobody has tried.
func TestInvalidatesVerification(t *testing.T) {
s := func(v string) *string { return &v }
i := func(v int) *int { return &v }
b := func(v bool) *bool { return &v }
for name, u := range map[string]Update{
"host": {SMTPHost: s("mail.example.com")},
"port": {SMTPPort: i(465)},
"username": {SMTPUsername: s("someone")},
"security": {SMTPSecurity: s("implicit_tls")},
"sender": {EmailFromAddress: s("new@example.com")},
} {
if !u.invalidatesVerification() {
t.Errorf("changing the %s must clear the verification", name)
}
}
for name, u := range map[string]Update{
"visibility": {WorkVisibility: s("team")},
"presence": {ShowActivityPresence: b(false)},
"from name": {EmailFromName: s("Acme")},
"claim hours": {ClaimExpiryHours: i(24)},
} {
if u.invalidatesVerification() {
t.Errorf("changing the %s should NOT force a re verification", name)
}
}
}
// Only the names this system knows may be stored, so a bounded credential store cannot become a general
// purpose blob table nobody audits.
func TestOnlyKnownSecretNames(t *testing.T) {
if !knownSecret(SecretSMTPPassword) {
t.Error("the smtp password must be a known secret name")
}
for _, bad := range []string{"", "anything", "aws_key", "smtp_password2", "SMTP_PASSWORD"} {
if knownSecret(bad) {
t.Errorf("%q must not be a storable secret name", bad)
}
}
}
// keyFor must return the active key for the active version, the previous key for the one before it, and
// NOTHING for anything else. Returning a key for an unknown version would mean opening a secret with the
// wrong key and reporting corruption instead of a configuration error.
func TestKeyForRotation(t *testing.T) {
active := make([]byte, security.AESKeySize)
prev := make([]byte, security.AESKeySize)
for i := range active {
active[i], prev[i] = 1, 2
}
s := NewStore(nil, active, prev, 7)
if got := s.keyFor(7); len(got) == 0 || got[0] != 1 {
t.Error("version 7 must resolve to the active key")
}
if got := s.keyFor(6); len(got) == 0 || got[0] != 2 {
t.Error("version 6 must resolve to the previous key")
}
for _, v := range []int{0, 5, 8, 99, -1} {
if s.keyFor(v) != nil {
t.Errorf("version %d must resolve to no key at all", v)
}
}
// With no keys at all, sealing is unavailable and nothing resolves.
empty := NewStore(nil, nil, nil, 1)
if empty.SealAvailable() {
t.Error("a store with no key must report sealing unavailable")
}
if empty.keyFor(1) != nil {
t.Error("a store with no key must resolve nothing")
}
// A key of the wrong length must not be treated as usable.
short := NewStore(nil, make([]byte, 16), nil, 1)
if short.SealAvailable() {
t.Error("a 16 byte key is not AES 256 and must not be accepted")
}
}
// The per user mailbox secret name must satisfy the database CHECK on org_secrets.name, which allows
// lowercase letters, digits, underscore and dot. A UUID's dashed form does NOT, so building the name from
// it would be refused at insert time by a constraint a long way from the code that made the mistake.
func TestIMAPSecretNameMatchesTheColumnConstraint(t *testing.T) {
id := uuid.MustParse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
name := IMAPSecretName(id)
if !knownSecret(name) {
t.Fatalf("%q is not accepted by its own validator", name)
}
// The CHECK is ^[a-z][a-z0-9_.]{1,62}$, asserted here character by character.
if len(name) < 2 || len(name) > 63 {
t.Errorf("name length %d is outside the constraint", len(name))
}
if name[0] < 'a' || name[0] > 'z' {
t.Errorf("name must start with a lowercase letter, got %q", name[0])
}
for i := 1; i < len(name); i++ {
c := name[i]
ok := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '.'
if !ok {
t.Errorf("character %q at %d violates the column constraint", c, i)
}
}
// Two users get two names.
other := IMAPSecretName(uuid.MustParse("00000000-0000-0000-0000-000000000001"))
if other == name {
t.Error("two users produced the same secret name")
}
// And nothing else in that shape is accepted.
for _, bad := range []string{
"imap_password.", "imap_password.short", "imap_password.zz",
"imap_password.6ba7b810-9dad-11d1-80b4-00c04fd430c8", "imap_password.ZZZZ", "aws_key",
} {
if knownSecret(bad) {
t.Errorf("%q must not be a storable secret name", bad)
}
}
}