rastrillo / idear Public

Clone
git clone https://amadan.net/rastrillo/idear

Plain git — no account needed to clone.

Download

Download this file

1package idear
2
3import (
4 "errors"
5 "testing"
6 "time"
7)
8
9// member is a small constructor for test fixtures: an active member
10// with the given id and role, or a deactivated one when deactivated is
11// true. Kept separate from the struct literals below so each case in
12// the table reads as data, not setup logic.
13func member(id int64, role Role, deactivated bool) *Member {
14 m := &Member{ID: id, Role: role}
15 if deactivated {
16 t := time.Unix(0, 0)
17 m.DeactivatedAt = &t
18 }
19 return m
20}
21
22// TestMayActOn_Matrix states, cell by cell, whether actor may act on
23// target. Every want is written down by hand rather than recomputed
24// from rank() or AtLeast() — a test that re-derives the implementation's
25// own comparison proves only that the comparison equals itself, which
26// is exactly the bug round 1's auditor found in the allow-list tests.
27func TestMayActOn_Matrix(t *testing.T) {
28 // Distinct ids on both sides so "different person" cases are never
29 // accidentally also self-action cases.
30 activeOwner := member(1, RoleOwner, false)
31 activeAdmin := member(2, RoleAdmin, false)
32 activeMember := member(3, RoleMember, false)
33
34 otherOwner := member(10, RoleOwner, false)
35 otherAdmin := member(20, RoleAdmin, false)
36 otherMember := member(30, RoleMember, false)
37
38 deactivatedOwner := member(1, RoleOwner, true)
39 deactivatedAdmin := member(2, RoleAdmin, true)
40
41 cases := []struct {
42 name string
43 actor *Member
44 target *Member
45 wantErr bool
46 }{
47 // The full 3x3 actor/target matrix, actor and target always
48 // distinct people.
49 {"owner acts on other owner", activeOwner, otherOwner, true},
50 {"owner acts on admin", activeOwner, otherAdmin, false},
51 {"owner acts on member", activeOwner, otherMember, false},
52 {"admin acts on owner", activeAdmin, otherOwner, true},
53 {"admin acts on other admin", activeAdmin, otherAdmin, true},
54 {"admin acts on member", activeAdmin, otherMember, false},
55 {"member acts on owner", activeMember, otherOwner, true},
56 {"member acts on admin", activeMember, otherAdmin, true},
57 {"member acts on other member", activeMember, otherMember, true},
58
59 // Self-action: refused for every rank, including the Owner.
60 {"owner acts on self", activeOwner, member(1, RoleOwner, false), true},
61 {"admin acts on self", activeAdmin, member(2, RoleAdmin, false), true},
62 {"member acts on self", activeMember, member(3, RoleMember, false), true},
63
64 // Inactive actor: refused regardless of rank, before rank is
65 // even considered.
66 {"deactivated owner acts on member", deactivatedOwner, otherMember, true},
67 {"deactivated admin acts on member", deactivatedAdmin, otherMember, true},
68
69 // Defensive: a nil actor or nil target can never be permitted.
70 {"nil actor", nil, otherMember, true},
71 {"nil target", activeOwner, nil, true},
72 }
73
74 for _, c := range cases {
75 t.Run(c.name, func(t *testing.T) {
76 err := MayActOn(c.actor, c.target)
77 if c.wantErr {
78 if err == nil {
79 t.Fatalf("MayActOn(%v, %v) = nil, want ErrForbidden", c.actor, c.target)
80 }
81 if !errors.Is(err, ErrForbidden) {
82 t.Fatalf("MayActOn(%v, %v) = %v, want wrapped ErrForbidden", c.actor, c.target, err)
83 }
84 return
85 }
86 if err != nil {
87 t.Fatalf("MayActOn(%v, %v) = %v, want nil", c.actor, c.target, err)
88 }
89 })
90 }
91}
92