| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "net/http" |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | "time" |
| 10 | ) |
| 11 | |
| 12 | func enrolStatus(t *testing.T, ts string, nonce string) bool { |
| 13 | t.Helper() |
| 14 | resp, err := http.Get(ts + "/api/enroll-status?nonce=" + nonce) |
| 15 | if err != nil { |
| 16 | t.Fatalf("status GET: %v", err) |
| 17 | } |
| 18 | defer resp.Body.Close() |
| 19 | if resp.StatusCode != http.StatusOK { |
| 20 | t.Fatalf("status code = %d", resp.StatusCode) |
| 21 | } |
| 22 | if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "*" { |
| 23 | t.Fatalf("ACAO = %q, want *", got) |
| 24 | } |
| 25 | var body struct{ Done bool } |
| 26 | if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { |
| 27 | t.Fatalf("decode: %v", err) |
| 28 | } |
| 29 | return body.Done |
| 30 | } |
| 31 | |
| 32 | func TestEnrolStatusRoundTrip(t *testing.T) { |
| 33 | ts, _ := newTestServer(t) |
| 34 | defer ts.Close() |
| 35 | |
| 36 | const nonce = "abcdefgh1234" |
| 37 | if enrolStatus(t, ts.URL, nonce) { |
| 38 | t.Fatal("unknown nonce reported done") |
| 39 | } |
| 40 | |
| 41 | resp, err := http.Post(ts.URL+"/api/enroll-done", "application/json", |
| 42 | strings.NewReader(`{"nonce":"`+nonce+`"}`)) |
| 43 | if err != nil { |
| 44 | t.Fatalf("done POST: %v", err) |
| 45 | } |
| 46 | resp.Body.Close() |
| 47 | if resp.StatusCode != http.StatusNoContent { |
| 48 | t.Fatalf("done POST code = %d", resp.StatusCode) |
| 49 | } |
| 50 | |
| 51 | if !enrolStatus(t, ts.URL, nonce) { |
| 52 | t.Fatal("marked nonce not reported done") |
| 53 | } |
| 54 | if enrolStatus(t, ts.URL, "differentnonce") { |
| 55 | t.Fatal("other nonce reported done") |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | func TestEnrolDoneRejectsBadNonces(t *testing.T) { |
| 60 | ts, _ := newTestServer(t) |
| 61 | defer ts.Close() |
| 62 | |
| 63 | for _, body := range []string{`{}`, `{"nonce":"short"}`, `{"nonce":"` + strings.Repeat("x", 200) + `"}`, `not json`} { |
| 64 | resp, err := http.Post(ts.URL+"/api/enroll-done", "application/json", strings.NewReader(body)) |
| 65 | if err != nil { |
| 66 | t.Fatalf("POST: %v", err) |
| 67 | } |
| 68 | resp.Body.Close() |
| 69 | if resp.StatusCode != http.StatusBadRequest { |
| 70 | t.Fatalf("body %q: code = %d, want 400", body, resp.StatusCode) |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | func TestEnrolMarksExpiry(t *testing.T) { |
| 76 | m := newEnrolMarks() |
| 77 | now := time.Now() |
| 78 | m.now = func() time.Time { return now } |
| 79 | m.mark("nonce-that-expires") |
| 80 | if !m.check("nonce-that-expires") { |
| 81 | t.Fatal("fresh mark not found") |
| 82 | } |
| 83 | now = now.Add(enrolMarkTTL + time.Minute) |
| 84 | if m.check("nonce-that-expires") { |
| 85 | t.Fatal("expired mark still reported done") |
| 86 | } |
| 87 | // And the next mark purges it from the map entirely. |
| 88 | m.mark("another-nonce") |
| 89 | m.mu.Lock() |
| 90 | _, still := m.done["nonce-that-expires"] |
| 91 | m.mu.Unlock() |
| 92 | if still { |
| 93 | t.Fatal("expired mark not purged") |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | func TestEnrolMarksCap(t *testing.T) { |
| 98 | m := newEnrolMarks() |
| 99 | for i := 0; i < enrolMarkCap; i++ { |
| 100 | m.mark("padpadpad" + strconv.Itoa(i)) |
| 101 | } |
| 102 | m.mark("overflow-nonce") |
| 103 | if m.check("overflow-nonce") { |
| 104 | t.Fatal("mark accepted past the cap") |
| 105 | } |
| 106 | } |
| 107 | |