Recently Written · git

recentlywritten

my writings (done recently)

git clone https://github.com/equwal/recentlywritten

Log | Files | Refs


accounts/store_test.go (3564 bytes)

1 package main
2 
3 import (
4 	"errors"
5 	"regexp"
6 	"testing"
7 	"time"
8 
9 	"pgregory.net/rapid"
10 )
11 
12 var tokenForm = regexp.MustCompile(`^[A-Z2-7]{26}$`)
13 
14 // TestTokensProperty checks that tokens are URL-safe, are all different,
15 // and that different tokens have different hashes.
16 func TestTokensProperty(t *testing.T) {
17 	seen := map[string]bool{}
18 	rapid.Check(t, func(t *rapid.T) {
19 		tok := newToken()
20 		if !tokenForm.MatchString(tok) {
21 			t.Fatalf("token %q is not 26 base32 characters", tok)
22 		}
23 		if seen[tok] {
24 			t.Fatalf("token %q came two times", tok)
25 		}
26 		seen[tok] = true
27 		other := rapid.String().Draw(t, "other")
28 		if other != tok && hashToken(other) == hashToken(tok) {
29 			t.Fatal("two tokens have one hash")
30 		}
31 	})
32 }
33 
34 // TestLoginTokenProperty checks that a sign-in token works one time for
35 // its address, and that no other string works.
36 func TestLoginTokenProperty(t *testing.T) {
37 	rapid.Check(t, func(rt *rapid.T) {
38 		e := newTestEnv(t, nil)
39 		email := rapid.StringMatching(`[a-z0-9]{1,10}@[a-z]{1,10}\.[a-z]{2,4}`).Draw(rt, "email")
40 		tok, err := createLoginToken(e.app.db, email, e.clock.now())
41 		if err != nil || tok == "" {
42 			rt.Fatalf("createLoginToken: %q, %v", tok, err)
43 		}
44 		guess := rapid.String().Filter(func(s string) bool { return s != tok }).Draw(rt, "guess")
45 		if _, err := consumeLoginToken(e.app.db, guess, e.clock.now()); !errors.Is(err, errNotFound) {
46 			rt.Fatalf("a wrong token gave %v", err)
47 		}
48 		e.advance(time.Duration(rapid.IntRange(0, int(loginTTL/time.Second)-1).Draw(rt, "wait")) * time.Second)
49 		u, err := consumeLoginToken(e.app.db, tok, e.clock.now())
50 		if err != nil || u.Email != email {
51 			rt.Fatalf("consume: %+v, %v", u, err)
52 		}
53 		if _, err := consumeLoginToken(e.app.db, tok, e.clock.now()); !errors.Is(err, errNotFound) {
54 			rt.Fatalf("second use gave %v", err)
55 		}
56 	})
57 }
58 
59 func TestLoginTokenExpires(t *testing.T) {
60 	e := newTestEnv(t, nil)
61 	tok, _ := createLoginToken(e.app.db, "a@example.com", e.clock.now())
62 	e.advance(loginTTL)
63 	if _, err := consumeLoginToken(e.app.db, tok, e.clock.now()); !errors.Is(err, errNotFound) {
64 		t.Fatalf("expired token gave %v", err)
65 	}
66 }
67 
68 func TestLoginTokenSpacing(t *testing.T) {
69 	e := newTestEnv(t, nil)
70 	if tok, _ := createLoginToken(e.app.db, "a@example.com", e.clock.now()); tok == "" {
71 		t.Fatal("first token is empty")
72 	}
73 	e.advance(loginSpacing - time.Second)
74 	if tok, _ := createLoginToken(e.app.db, "a@example.com", e.clock.now()); tok != "" {
75 		t.Fatal("second token came too soon")
76 	}
77 	if tok, _ := createLoginToken(e.app.db, "b@example.com", e.clock.now()); tok == "" {
78 		t.Fatal("other address got no token")
79 	}
80 	e.advance(time.Second)
81 	if tok, _ := createLoginToken(e.app.db, "a@example.com", e.clock.now()); tok == "" {
82 		t.Fatal("no token after the spacing")
83 	}
84 }
85 
86 func TestSignInTwiceKeepsOneUser(t *testing.T) {
87 	e := newTestEnv(t, nil)
88 	a := e.signUp(t, "a@example.com")
89 	e.advance(loginSpacing)
90 	b := e.signUp(t, "a@example.com")
91 	if a.ID != b.ID || a.FeedToken != b.FeedToken || a.CreatedAt != b.CreatedAt {
92 		t.Fatalf("second sign-in changed the user: %+v, %+v", a, b)
93 	}
94 }
95 
96 func TestSessionExpires(t *testing.T) {
97 	e := newTestEnv(t, nil)
98 	u := e.signUp(t, "a@example.com")
99 	s, err := createSession(e.app.db, u.ID, e.clock.now())
100 	if err != nil {
101 		t.Fatal(err)
102 	}
103 	if got, err := sessionUser(e.app.db, s, e.clock.now()); err != nil || got.ID != u.ID {
104 		t.Fatalf("sessionUser: %+v, %v", got, err)
105 	}
106 	e.advance(sessionTTL)
107 	if _, err := sessionUser(e.app.db, s, e.clock.now()); !errors.Is(err, errNotFound) {
108 		t.Fatalf("expired session gave %v", err)
109 	}
110 }