test: add comprehensive device token regeneration tests
- Test successful token regeneration - Verify old tokens are invalidated after regeneration - Test unauthorized and forbidden access scenarios - Test not found and device type-specific behavior - Validate sync URLs contain new tokens
This commit is contained in:
@@ -322,3 +322,207 @@ func TestRejectDeviceRegistration(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "device registration rejected", rejectResponse["message"], "Should confirm rejection message")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_Success(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Create a device
|
||||
device := setup.CreateDevice(t, "Test Device", "koreader", "test-device-123")
|
||||
oldToken := device.AuthToken
|
||||
|
||||
// Regenerate token
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", device.ID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should regenerate token")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
|
||||
assert.True(t, response["message"].(string) != "", "Should have success message")
|
||||
|
||||
newToken, ok := response["auth_token"].(string)
|
||||
assert.True(t, ok, "Should have auth_token")
|
||||
assert.NotEmpty(t, newToken, "New token should not be empty")
|
||||
assert.NotEqual(t, oldToken, newToken, "New token should be different from old token")
|
||||
|
||||
// Verify device info is returned
|
||||
deviceInfo, ok := response["device"].(map[string]interface{})
|
||||
assert.True(t, ok, "Should have device info")
|
||||
assert.Equal(t, "Test Device", deviceInfo["device_name"], "Should return device name")
|
||||
|
||||
// Verify sync URLs are returned
|
||||
syncURLs, ok := response["sync_urls"].(map[string]interface{})
|
||||
assert.True(t, ok, "Should have sync_urls")
|
||||
assert.Contains(t, syncURLs, "progress", "Should have progress URL")
|
||||
assert.Contains(t, syncURLs, "metadata", "Should have metadata URL")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_OldTokenInvalidated(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Create a device
|
||||
device := setup.CreateDevice(t, "Test Device", "koreader", "test-device-invalidated")
|
||||
|
||||
// Test old token works initially
|
||||
req1 := httptest.NewRequest("POST", "/api/sync/koreader/progress", nil)
|
||||
req1.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
||||
req1.Header.Set("Content-Type", "application/json")
|
||||
rec1 := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec1, req1)
|
||||
// May fail for other reasons (no data), but should not be unauthorized
|
||||
assert.NotEqual(t, http.StatusUnauthorized, rec1.Code, "Old token should work initially")
|
||||
|
||||
// Regenerate token
|
||||
req2 := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", device.ID.String()), nil)
|
||||
req2.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec2 := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec2, req2)
|
||||
assert.Equal(t, http.StatusOK, rec2.Code, "Should regenerate token")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec2.Body.Bytes(), &response)
|
||||
newToken := response["auth_token"].(string)
|
||||
|
||||
// Test old token no longer works
|
||||
req3 := httptest.NewRequest("POST", "/api/sync/koreader/progress", nil)
|
||||
req3.Header.Set("Authorization", "Bearer "+device.AuthToken)
|
||||
req3.Header.Set("Content-Type", "application/json")
|
||||
rec3 := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec3, req3)
|
||||
assert.Equal(t, http.StatusUnauthorized, rec3.Code, "Old token should be invalid after regeneration")
|
||||
|
||||
// Test new token works
|
||||
req4 := httptest.NewRequest("POST", "/api/sync/koreader/progress", nil)
|
||||
req4.Header.Set("Authorization", "Bearer "+newToken)
|
||||
req4.Header.Set("Content-Type", "application/json")
|
||||
rec4 := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec4, req4)
|
||||
// May fail for other reasons, but should not be unauthorized
|
||||
assert.NotEqual(t, http.StatusUnauthorized, rec4.Code, "New token should work")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_Unauthorized(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
device := setup.CreateDevice(t, "Test Device", "koreader", "test-device-unauth")
|
||||
|
||||
// Try to regenerate without JWT token
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", device.ID.String()), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rec.Code, "Should require authentication")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
assert.Contains(t, response, "error", "Should return error message")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_Forbidden(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Create device for user 1
|
||||
device1 := setup.CreateDevice(t, "User1 Device", "koreader", "user1-device")
|
||||
|
||||
// Create a second user with different credentials
|
||||
ctx := context.Background()
|
||||
passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq"
|
||||
_, err := setup.DB.CreateUser(ctx, database.CreateUserParams{
|
||||
Email: "differentuser@example.com",
|
||||
Username: "differentuser",
|
||||
PasswordHash: passwordHash,
|
||||
FirstName: pgtype.Text{String: "Different", Valid: true},
|
||||
LastName: pgtype.Text{String: "User", Valid: true},
|
||||
Role: "user",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Login as user 2
|
||||
loginRequest := map[string]interface{}{
|
||||
"login": "differentuser@example.com",
|
||||
"password": "Test@Pass123!",
|
||||
}
|
||||
loginBody, _ := json.Marshal(loginRequest)
|
||||
loginReq := httptest.NewRequest("POST", "/api/auth/login", bytes.NewReader(loginBody))
|
||||
loginReq.Header.Set("Content-Type", "application/json")
|
||||
loginRec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(loginRec, loginReq)
|
||||
|
||||
var loginResponse map[string]interface{}
|
||||
json.Unmarshal(loginRec.Body.Bytes(), &loginResponse)
|
||||
user2Token := loginResponse["access_token"].(string)
|
||||
|
||||
// Try to regenerate user 1's device with user 2's token
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", device1.ID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+user2Token)
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code, "Should forbid access to other user's device")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
assert.Contains(t, response, "error", "Should return error message")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_NotFound(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
fakeDeviceID, _ := uuid.NewUUID()
|
||||
|
||||
// Try to regenerate non-existent device
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", fakeDeviceID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, rec.Code, "Should return not found")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
assert.Contains(t, response, "error", "Should return error message")
|
||||
}
|
||||
|
||||
func TestRegenerateDeviceToken_KoboDevice(t *testing.T) {
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Create a Kobo device
|
||||
koboDevice := setup.CreateDevice(t, "Test Kobo", "kobo", "test-kobo-regen")
|
||||
oldToken := koboDevice.AuthToken
|
||||
|
||||
// Regenerate token
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s/regenerate-token", koboDevice.ID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec := httptest.NewRecorder()
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should regenerate token")
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(rec.Body.Bytes(), &response)
|
||||
|
||||
newToken := response["auth_token"].(string)
|
||||
assert.NotEqual(t, oldToken, newToken, "New token should be different")
|
||||
|
||||
// Verify Kobo sync URLs are returned
|
||||
syncURLs, ok := response["sync_urls"].(map[string]interface{})
|
||||
assert.True(t, ok, "Should have sync_urls")
|
||||
assert.Contains(t, syncURLs, "sync_url", "Should have sync_url")
|
||||
assert.Contains(t, syncURLs, "markup", "Should have markup URL")
|
||||
assert.Contains(t, syncURLs, "bookmark", "Should have bookmark URL")
|
||||
assert.Contains(t, syncURLs, "init", "Should have init URL")
|
||||
|
||||
// Verify URLs contain new token
|
||||
syncURL := syncURLs["sync_url"].(string)
|
||||
assert.Contains(t, syncURL, newToken, "Sync URL should contain new token")
|
||||
assert.NotContains(t, syncURL, oldToken, "Sync URL should not contain old token")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user