package certwarm import ( "strings" "testing" "time" ) func TestKeyAndHostFromKeyRoundTrip(t *testing.T) { for _, host := range []string{"www.customer.com", "xn--bcher-kva.example", "a.b.c.example.org"} { if got := HostFromKey(Key(host)); got != host { t.Errorf("HostFromKey(Key(%q)) = %q", host, got) } } } // A listing under the prefix can contain anything an operator or a future // build put there. Everything that is not exactly one host record must come // back as "" so the caller skips it rather than treating it as a hostname — // a nested key decoded as a host would be pruned as "not in the warm set", // which is a delete of somebody else's object. func TestHostFromKeyRefusesEverythingElse(t *testing.T) { for _, key := range []string{ "control/certs/example.com", "control/cert-warm/", "control/cert-warm/.json", "control/cert-warm/nested/host.json", "control/cert-warm/host.txt", "cert-warm/host.json", "", } { if got := HostFromKey(key); got != "" { t.Errorf("HostFromKey(%q) = %q, want \"\"", key, got) } } } func TestEncodeStampsVersionAndTime(t *testing.T) { now := time.Unix(1_700_000_000, 0) body, err := Record{Host: "a.example"}.Encode(now) if err != nil { t.Fatal(err) } got, err := Parse(body) if err != nil { t.Fatal(err) } if got.V != V || got.WrittenAt != now.Unix() { t.Errorf("Encode = %+v; want v%d written_at %d", got, V, now.Unix()) } // An unset class must never reach the wire: a reader branching on it // would see "" and fall through every case. if got.Class != ClassOther { t.Errorf("Class = %q, want %q", got.Class, ClassOther) } } func TestEncodeTrimsRemoteErrorText(t *testing.T) { body, err := Record{Host: "a.example", Error: strings.Repeat("x", ErrorMax*3)}.Encode(time.Unix(1, 0)) if err != nil { t.Fatal(err) } got, err := Parse(body) if err != nil { t.Fatal(err) } if len(got.Error) != ErrorMax { t.Errorf("Error length = %d, want %d — an unbounded CA string reached the bucket", len(got.Error), ErrorMax) } } // V<1 is refused (a record with no version is not one of ours); a HIGHER // version parses, because the wire is additive and an older console must // degrade to the fields it knows rather than fail the whole fold. func TestParseVersionBounds(t *testing.T) { if _, err := Parse([]byte(`{"host":"a.example"}`)); err == nil { t.Error("a record with no version parsed") } got, err := Parse([]byte(`{"v":99,"host":"a.example","attempts":4}`)) if err != nil { t.Fatalf("a newer version failed to parse: %v", err) } if got.Attempts != 4 { t.Errorf("Attempts = %d, want 4", got.Attempts) } } // Fresh is judged against the LATER of written-at and next-attempt, which is // what lets a deliberate twelve-hour rate-limit wait stay authoritative // while a wedged issuer's record eventually stops being believed. func TestFreshUsesTheNextAttemptDeadline(t *testing.T) { base := time.Unix(1_700_000_000, 0) rateLimited := Record{WrittenAt: base.Unix(), NextAttemptAt: base.Add(12 * time.Hour).Unix()} if !rateLimited.Fresh(base.Add(6 * time.Hour)) { t.Error("a record mid-way through its own backoff read as stale") } if rateLimited.Fresh(base.Add(12*time.Hour + Grace + time.Second)) { t.Error("a record past its next attempt plus grace still read as fresh — a wedged issuer would report as trying") } // No stamps at all is not evidence of anything. if (Record{}).Fresh(base) { t.Error("an unstamped record read as fresh") } }