test(devices): integration coverage for the pairing-code flow
Replaces the reject-route test (behavior removed) with coverage of the replacement flow, run against the shared dev DB per the doctrine: fresh per-run regular user for deterministic claims, uuid-suffixed identifiers so UNIQUE(device_identifier) stays clear across runs, and device rows cascade away with the fresh user — testuser is untouched. - TestPairingClaimHappyPath: lookup normalizes spaced digits, does not bind; claim binds to the claimer only; the device's status poll then delivers a token matching the stored row. - TestPairingWrongCodeIsGenericAndNonDestructive: a miss answers the generic invalid-code error and leaves the real code claimable. - TestPairingClaimBindsClaimerOnly: the admin account has no device row for another user's claim, and the used code answers 409. - TestPairingReRegisterRetiresPriorCode: the old code and its registration UUID both die; the fresh code claims. - TestPairingCancelByRegistrationUUID: cancel is idempotent, kills only its own registration (code and poll), and leaves a sibling registration untouched. - TestPairingAttemptRateLimit: 13th lookup/claim attempt in a minute is refused with 429 (uses the fresh regular user — the limiter is process-global, so sibling tests sharing a token would otherwise pre-spend the budget). - TestListPendingRegistrations now pins the invisibility invariant: a live registration appears in no account's pending list, total 0. - TestRejectDeviceRegistrationRemoved pins the reject route's removal (404/405).
This commit is contained in:
1 parent
fef79b1836
commit
3e98fadb9f
2 files changed
+298
-14
No files matched your search
@@ -237,10 +237,25 @@ func TestDeviceAuthentication(t *testing.T) {
|
||||
func TestListPendingRegistrations(t *testing.T) {
|
||||
setup := setupTestServer(t)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/devices/pending", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.Token)
|
||||
// A live registration must NOT appear in any account's pending list:
|
||||
// the broadcast is what let any user claim (or reject) someone else's
|
||||
// device. Pairing codes replaced it — see device_pairing_test.go.
|
||||
regRequest := map[string]interface{}{
|
||||
"device_name": "Broadcast Test Device",
|
||||
"device_type": "koreader",
|
||||
"device_identifier": fmt.Sprintf("broadcast-test-%s", uuid.New().String()),
|
||||
}
|
||||
regBody, _ := json.Marshal(regRequest)
|
||||
req := httptest.NewRequest("POST", "/api/devices/register", bytes.NewReader(regBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusCreated, rec.Code, "Should initiate registration")
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/devices/pending", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.Token)
|
||||
rec = httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should list pending registrations")
|
||||
|
||||
@@ -248,8 +263,9 @@ func TestListPendingRegistrations(t *testing.T) {
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
|
||||
pending, ok := response["pending_registrations"].([]interface{})
|
||||
assert.True(t, ok, "Should have pending_registrations array")
|
||||
assert.NotNil(t, pending, "Pending registrations should not be nil")
|
||||
require.True(t, ok, "Should have pending_registrations array")
|
||||
assert.Empty(t, pending, "Unclaimed registrations must be invisible to every account")
|
||||
assert.Equal(t, float64(0), response["total"], "Total must stay zero while a registration is live")
|
||||
}
|
||||
|
||||
// Regression test for the "approve returns 500 after app reinstall" bug:
|
||||
@@ -370,13 +386,18 @@ func TestApproveDeviceRegistration(t *testing.T) {
|
||||
assert.True(t, approveResponse["approved"].(bool), "Should confirm approval")
|
||||
}
|
||||
|
||||
func TestRejectDeviceRegistration(t *testing.T) {
|
||||
// TestRejectDeviceRegistrationRemoved pins the removal: the reject route is
|
||||
// gone because unclaimed registrations are invisible since pairing codes
|
||||
// took over — there is nothing left for anyone to reject. See
|
||||
// device_pairing_test.go for the replacement flow (cancel by the
|
||||
// registering device itself).
|
||||
func TestRejectDeviceRegistrationRemoved(t *testing.T) {
|
||||
setup := setupTestServer(t)
|
||||
|
||||
regRequest := map[string]interface{}{
|
||||
"device_name": "Test Device for Rejection",
|
||||
"device_type": "koreader",
|
||||
"device_identifier": "test-rejection-12345",
|
||||
"device_identifier": fmt.Sprintf("test-rejection-%s", uuid.New().String()),
|
||||
}
|
||||
regBody, _ := json.Marshal(regRequest)
|
||||
|
||||
@@ -385,25 +406,23 @@ func TestRejectDeviceRegistration(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, rec.Code, "Should initiate device registration")
|
||||
require.Equal(t, http.StatusCreated, rec.Code, "Should initiate device registration")
|
||||
|
||||
var regResponse map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), ®Response)
|
||||
|
||||
registrationID, ok := regResponse["registration_id"].(string)
|
||||
assert.True(t, ok, "Should have registration_id")
|
||||
require.True(t, ok, "Should have registration_id")
|
||||
|
||||
req = httptest.NewRequest("POST", fmt.Sprintf("/api/devices/reject/%s", registrationID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.Token)
|
||||
rec = httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should reject device registration")
|
||||
|
||||
var rejectResponse map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &rejectResponse)
|
||||
|
||||
assert.Equal(t, "device registration rejected", rejectResponse["message"], "Should confirm rejection message")
|
||||
// 404 = no route, 405 = path-shaped collision without POST — either
|
||||
// proves the reject surface is gone.
|
||||
assert.True(t, rec.Code == http.StatusNotFound || rec.Code == http.StatusMethodNotAllowed,
|
||||
"Reject route must not exist (registration invisible, nothing to reject); got %d", rec.Code)
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_Success(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user