| 1 | package certwarm |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | func TestKeyAndHostFromKeyRoundTrip(t *testing.T) { |
| 10 | for _, host := range []string{"www.customer.com", "xn--bcher-kva.example", "a.b.c.example.org"} { |
| 11 | if got := HostFromKey(Key(host)); got != host { |
| 12 | t.Errorf("HostFromKey(Key(%q)) = %q", host, got) |
| 13 | } |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | // A listing under the prefix can contain anything an operator or a future |
| 18 | // build put there. Everything that is not exactly one host record must come |
| 19 | // back as "" so the caller skips it rather than treating it as a hostname — |
| 20 | // a nested key decoded as a host would be pruned as "not in the warm set", |
| 21 | // which is a delete of somebody else's object. |
| 22 | func TestHostFromKeyRefusesEverythingElse(t *testing.T) { |
| 23 | for _, key := range []string{ |
| 24 | "control/certs/example.com", |
| 25 | "control/cert-warm/", |
| 26 | "control/cert-warm/.json", |
| 27 | "control/cert-warm/nested/host.json", |
| 28 | "control/cert-warm/host.txt", |
| 29 | "cert-warm/host.json", |
| 30 | "", |
| 31 | } { |
| 32 | if got := HostFromKey(key); got != "" { |
| 33 | t.Errorf("HostFromKey(%q) = %q, want \"\"", key, got) |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | func TestEncodeStampsVersionAndTime(t *testing.T) { |
| 39 | now := time.Unix(1_700_000_000, 0) |
| 40 | body, err := Record{Host: "a.example"}.Encode(now) |
| 41 | if err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | got, err := Parse(body) |
| 45 | if err != nil { |
| 46 | t.Fatal(err) |
| 47 | } |
| 48 | if got.V != V || got.WrittenAt != now.Unix() { |
| 49 | t.Errorf("Encode = %+v; want v%d written_at %d", got, V, now.Unix()) |
| 50 | } |
| 51 | // An unset class must never reach the wire: a reader branching on it |
| 52 | // would see "" and fall through every case. |
| 53 | if got.Class != ClassOther { |
| 54 | t.Errorf("Class = %q, want %q", got.Class, ClassOther) |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func TestEncodeTrimsRemoteErrorText(t *testing.T) { |
| 59 | body, err := Record{Host: "a.example", Error: strings.Repeat("x", ErrorMax*3)}.Encode(time.Unix(1, 0)) |
| 60 | if err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | got, err := Parse(body) |
| 64 | if err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | if len(got.Error) != ErrorMax { |
| 68 | t.Errorf("Error length = %d, want %d — an unbounded CA string reached the bucket", len(got.Error), ErrorMax) |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // V<1 is refused (a record with no version is not one of ours); a HIGHER |
| 73 | // version parses, because the wire is additive and an older console must |
| 74 | // degrade to the fields it knows rather than fail the whole fold. |
| 75 | func TestParseVersionBounds(t *testing.T) { |
| 76 | if _, err := Parse([]byte(`{"host":"a.example"}`)); err == nil { |
| 77 | t.Error("a record with no version parsed") |
| 78 | } |
| 79 | got, err := Parse([]byte(`{"v":99,"host":"a.example","attempts":4}`)) |
| 80 | if err != nil { |
| 81 | t.Fatalf("a newer version failed to parse: %v", err) |
| 82 | } |
| 83 | if got.Attempts != 4 { |
| 84 | t.Errorf("Attempts = %d, want 4", got.Attempts) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | // Fresh is judged against the LATER of written-at and next-attempt, which is |
| 89 | // what lets a deliberate twelve-hour rate-limit wait stay authoritative |
| 90 | // while a wedged issuer's record eventually stops being believed. |
| 91 | func TestFreshUsesTheNextAttemptDeadline(t *testing.T) { |
| 92 | base := time.Unix(1_700_000_000, 0) |
| 93 | rateLimited := Record{WrittenAt: base.Unix(), NextAttemptAt: base.Add(12 * time.Hour).Unix()} |
| 94 | if !rateLimited.Fresh(base.Add(6 * time.Hour)) { |
| 95 | t.Error("a record mid-way through its own backoff read as stale") |
| 96 | } |
| 97 | if rateLimited.Fresh(base.Add(12*time.Hour + Grace + time.Second)) { |
| 98 | t.Error("a record past its next attempt plus grace still read as fresh — a wedged issuer would report as trying") |
| 99 | } |
| 100 | // No stamps at all is not evidence of anything. |
| 101 | if (Record{}).Fresh(base) { |
| 102 | t.Error("an unstamped record read as fresh") |
| 103 | } |
| 104 | } |
| 105 | |