Skip to content

Commit aafeafd

Browse files
committed
test: pin CountUsers against the user list in every device-count mode
CountUsers keeps its own copy of the device clause and had drifted from the one that enforces — it honoured neither the count mode nor the grace, so the dashboard's "active" total disagreed with the list rendered beside it. That was fixed in 6fa0043 with nothing pinning it. Getting the test to fail for the right reason took two tries. Setting the mode before stamping leaves device_over_since at zero, and the "device_over_since = 0" term then admits the user on its own — so a clause with the mode term removed still passed. The mode is now set after the stamp, reproducing the real state an operator passes through when they switch to hwid: a row still carrying an armed stamp under a mode that does not enforce it. The second account, over the limit but still inside the grace, is what pins the grace term; without it a clause that cuts immediately reads the same as one that waits. Both halves verified by breaking the corresponding term and watching the test fail.
1 parent d4f4a11 commit aafeafd

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

internal/store/count_users_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,65 @@ func TestCountUsersOnlineWindow(t *testing.T) {
199199
t.Errorf("online = %d, want 2 (the two-device user counted once, plus the edge)", got.Online)
200200
}
201201
}
202+
203+
// CountUsers keeps its own copy of the device clause, so it has to be pinned against the
204+
// list beside it in every mode — not just the default. It once honoured neither the
205+
// count mode nor the grace, which made the dashboard's "active" total disagree with the
206+
// list rendered next to it by one user per affected account.
207+
func TestCountUsersAgreesInEveryDeviceMode(t *testing.T) {
208+
for _, mode := range []string{model.DeviceCountAuto, model.DeviceCountHWID, model.DeviceCountBoth} {
209+
t.Run(mode, func(t *testing.T) {
210+
st := dcStore(t)
211+
now := time.Now().Unix()
212+
// One account well over a one-device limit, stamped from before the grace
213+
// expired, so the device dimension is live rather than merely present.
214+
dcUser(t, st, "shared",
215+
ConnectionHit{IP: "10.0.0.1", SeenAt: now},
216+
ConnectionHit{IP: "10.0.0.2", SeenAt: now},
217+
ConnectionHit{IP: "10.0.0.3", SeenAt: now},
218+
)
219+
if err := st.StampDeviceOverLimit(now - model.DeviceLimitGrace - 10); err != nil {
220+
t.Fatalf("stamp: %v", err)
221+
}
222+
// The mode is set AFTER the stamp on purpose. Switching to "hwid" clears
223+
// stamps, but only when something next runs the stamp — until then the row
224+
// carries an armed stamp under a mode that does not enforce it, and that is
225+
// the exact state where a clause missing the mode reads differently from a
226+
// clause that has it. Setting the mode first would leave device_over_since
227+
// at zero, which makes both readings agree for a reason that has nothing to
228+
// do with what this test is for.
229+
if err := st.SetDeviceCountMode(mode); err != nil {
230+
t.Fatalf("mode: %v", err)
231+
}
232+
// A second account over the limit but only just — still inside the grace,
233+
// so nothing has happened to it yet. This is the half that pins the grace
234+
// term rather than the mode term: without it, a clause that cuts the moment
235+
// someone goes over reads the same as one that waits.
236+
fresh := dcUser(t, st, "fresh",
237+
ConnectionHit{IP: "10.9.0.1", SeenAt: now},
238+
ConnectionHit{IP: "10.9.0.2", SeenAt: now},
239+
)
240+
if _, err := st.db.Exec(
241+
`UPDATE users SET device_over_since = ? WHERE id = ?`, now, fresh.ID); err != nil {
242+
t.Fatalf("arm: %v", err)
243+
}
244+
got, err := st.CountUsers(now)
245+
if err != nil {
246+
t.Fatalf("count: %v", err)
247+
}
248+
users, err := st.ListUsers()
249+
if err != nil {
250+
t.Fatalf("list: %v", err)
251+
}
252+
var listActive int
253+
for _, u := range users {
254+
if u.Status == model.StatusActive {
255+
listActive++
256+
}
257+
}
258+
if listActive != got.Active {
259+
t.Errorf("ListUsers says %d active, CountUsers says %d", listActive, got.Active)
260+
}
261+
})
262+
}
263+
}

0 commit comments

Comments
 (0)