fix: Fix device response fields and add missing approved confirmation
feat: Improve device test infrastructure with setupDeviceTest helper refactor: Standardize pending registrations API response field names
This commit is contained in:
@@ -68,7 +68,7 @@ func TestDeviceRegistrationFlow(t *testing.T) {
|
||||
// Step 3: Login as user to approve device
|
||||
loginRequest := map[string]interface{}{
|
||||
"login": "testuser@example.com",
|
||||
"password": "testpass123",
|
||||
"password": "Test@Pass123!",
|
||||
}
|
||||
loginBody, _ := json.Marshal(loginRequest)
|
||||
|
||||
@@ -115,34 +115,17 @@ func TestDeviceRegistrationFlow(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListDevices(t *testing.T) {
|
||||
ts, db, _ := setupTestServer(t)
|
||||
defer ts.Close()
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Login to get token
|
||||
token := loginTestUser(t, ts, db)
|
||||
|
||||
// Create a device directly in the database
|
||||
userID := getTestUserID(t, db)
|
||||
|
||||
deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
|
||||
_, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{
|
||||
UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
|
||||
DeviceName: "Test Device",
|
||||
DeviceType: "koreader",
|
||||
DeviceIdentifier: "test-device-123",
|
||||
AuthToken: deviceToken,
|
||||
SyncEnabled: pgtype.Bool{Bool: true, Valid: true},
|
||||
AutoSync: pgtype.Bool{Bool: true, Valid: true},
|
||||
SyncFrequencyMinutes: pgtype.Int4{Int32: 5, Valid: true},
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
assert.NoError(t, err, "Should create device")
|
||||
// Create a device using the setup helper
|
||||
_ = setup.CreateDevice(t, "Test Device", "koreader", "test-device-123")
|
||||
|
||||
// List devices
|
||||
req := httptest.NewRequest("GET", "/api/devices", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec := httptest.NewRecorder()
|
||||
ts.Config.Handler.ServeHTTP(rec, req)
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should list devices")
|
||||
|
||||
@@ -160,33 +143,11 @@ func TestListDevices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateDevice(t *testing.T) {
|
||||
ts, db, _ := setupTestServer(t)
|
||||
defer ts.Close()
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Login to get token
|
||||
token := loginTestUser(t, ts, db)
|
||||
|
||||
// Create a device directly in the database
|
||||
userID := getTestUserID(t, db)
|
||||
|
||||
deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
|
||||
device, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{
|
||||
UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
|
||||
DeviceName: "Test Device",
|
||||
DeviceType: "koreader",
|
||||
DeviceIdentifier: "test-device-123",
|
||||
AuthToken: deviceToken,
|
||||
SyncEnabled: pgtype.Bool{Bool: true, Valid: true},
|
||||
AutoSync: pgtype.Bool{Bool: true, Valid: true},
|
||||
SyncFrequencyMinutes: pgtype.Int4{Int32: 5, Valid: true},
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
assert.NoError(t, err, "Should create device")
|
||||
|
||||
// Get the device ID from the created device
|
||||
deviceIDBytes := device.ID.Bytes[0:16]
|
||||
deviceID, err := uuid.FromBytes(deviceIDBytes)
|
||||
assert.NoError(t, err, "Should parse device ID")
|
||||
// Create a device using the setup helper
|
||||
device := setup.CreateDevice(t, "Test Device", "koreader", "test-device-123")
|
||||
|
||||
// Update device
|
||||
updateRequest := map[string]interface{}{
|
||||
@@ -196,11 +157,11 @@ func TestUpdateDevice(t *testing.T) {
|
||||
}
|
||||
updateBody, _ := json.Marshal(updateRequest)
|
||||
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s", deviceID.String()), bytes.NewReader(updateBody))
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/devices/%s", device.ID.String()), bytes.NewReader(updateBody))
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
ts.Config.Handler.ServeHTTP(rec, req)
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code, "Should update device")
|
||||
|
||||
@@ -216,44 +177,23 @@ func TestUpdateDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteDevice(t *testing.T) {
|
||||
ts, db, _ := setupTestServer(t)
|
||||
defer ts.Close()
|
||||
setup := setupDeviceTest(t)
|
||||
defer setup.Server.Close()
|
||||
|
||||
// Login to get token
|
||||
token := loginTestUser(t, ts, db)
|
||||
|
||||
// Create a device directly in the database
|
||||
userID := getTestUserID(t, db)
|
||||
|
||||
deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
|
||||
newDevice, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{
|
||||
UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
|
||||
DeviceName: "Test Device",
|
||||
DeviceType: "koreader",
|
||||
DeviceIdentifier: "test-device-123",
|
||||
AuthToken: deviceToken,
|
||||
SyncEnabled: pgtype.Bool{Bool: true, Valid: true},
|
||||
AutoSync: pgtype.Bool{Bool: true, Valid: true},
|
||||
SyncFrequencyMinutes: pgtype.Int4{Int32: 5, Valid: true},
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
assert.NoError(t, err, "Should create device")
|
||||
|
||||
// Get the device ID from the created device
|
||||
deviceIDBytes := newDevice.ID.Bytes[0:16]
|
||||
deviceID, err := uuid.FromBytes(deviceIDBytes)
|
||||
assert.NoError(t, err, "Should parse device ID")
|
||||
// Create a device using the setup helper
|
||||
device := setup.CreateDevice(t, "Test Device", "koreader", "test-device-123")
|
||||
|
||||
// Delete device
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/devices/%s", deviceID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/devices/%s", device.ID.String()), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+setup.UserToken)
|
||||
rec := httptest.NewRecorder()
|
||||
ts.Config.Handler.ServeHTTP(rec, req)
|
||||
setup.Server.Config.Handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusNoContent, rec.Code, "Should delete device")
|
||||
|
||||
// Verify device is deleted
|
||||
_, err = db.GetDevice(context.Background(), newDevice.ID)
|
||||
pgDeviceID := pgtype.UUID{Bytes: [16]byte(device.ID), Valid: true}
|
||||
_, err := setup.DB.GetDevice(context.Background(), pgDeviceID)
|
||||
assert.Error(t, err, "Device should be deleted")
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -37,6 +38,33 @@ func (cv *CustomValidator) Validate(i interface{}) error {
|
||||
return cv.validator.Struct(i)
|
||||
}
|
||||
|
||||
// TestDeviceSetup provides a complete, isolated test environment for device tests
|
||||
type TestDeviceSetup struct {
|
||||
Server *httptest.Server
|
||||
DB *database.Queries
|
||||
Config *config.Config
|
||||
User UserTestData
|
||||
Device DeviceTestData
|
||||
UserToken string
|
||||
}
|
||||
|
||||
type UserTestData struct {
|
||||
ID uuid.UUID
|
||||
Email string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
}
|
||||
|
||||
type DeviceTestData struct {
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Type string
|
||||
Identifier string
|
||||
AuthToken string
|
||||
PGType database.Devices
|
||||
}
|
||||
|
||||
// Helper functions for testing
|
||||
func containsPrefix(s, prefix string) bool {
|
||||
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
|
||||
@@ -100,6 +128,118 @@ func getCachePath() string {
|
||||
return "/app/cache/kepub"
|
||||
}
|
||||
|
||||
// setupDeviceTest creates a complete test environment for device tests
|
||||
func setupDeviceTest(t *testing.T) *TestDeviceSetup {
|
||||
ts, db, cfg := setupTestServer(t)
|
||||
|
||||
// Create user ONCE with known credentials
|
||||
user := createTestUserOnce(t, db)
|
||||
|
||||
// Login to get token
|
||||
token := loginUserWithCredentials(t, ts, user.Email, user.Password)
|
||||
|
||||
return &TestDeviceSetup{
|
||||
Server: ts,
|
||||
DB: db,
|
||||
Config: cfg,
|
||||
User: user,
|
||||
UserToken: token,
|
||||
}
|
||||
}
|
||||
|
||||
// createTestUserOnce creates a test user with deterministic UUID
|
||||
func createTestUserOnce(t *testing.T, db *database.Queries) UserTestData {
|
||||
ctx := context.Background()
|
||||
|
||||
// Clean up any existing test user first
|
||||
existingUser, err := db.GetUserByEmail(ctx, "testuser@example.com")
|
||||
if err == nil {
|
||||
db.DeleteUser(ctx, existingUser.ID)
|
||||
}
|
||||
|
||||
// Create user with known credentials
|
||||
passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq"
|
||||
user, err := db.CreateUser(ctx, database.CreateUserParams{
|
||||
Email: "testuser@example.com",
|
||||
Username: "testuser",
|
||||
PasswordHash: passwordHash,
|
||||
FirstName: pgtype.Text{String: "Test", Valid: true},
|
||||
LastName: pgtype.Text{String: "User", Valid: true},
|
||||
Role: "admin",
|
||||
})
|
||||
require.NoError(t, err, "Should create test user")
|
||||
|
||||
// Get the user ID from the created user
|
||||
userUUID, err := uuid.FromBytes(user.ID.Bytes[0:16])
|
||||
require.NoError(t, err, "Should parse user UUID")
|
||||
|
||||
return UserTestData{
|
||||
ID: userUUID,
|
||||
Email: "testuser@example.com",
|
||||
Username: "testuser",
|
||||
Password: "Test@Pass123!",
|
||||
}
|
||||
}
|
||||
|
||||
// loginUserWithCredentials performs explicit login with provided credentials
|
||||
func loginUserWithCredentials(t *testing.T, ts *httptest.Server, email, password string) string {
|
||||
loginRequest := map[string]interface{}{
|
||||
"login": email,
|
||||
"password": password,
|
||||
}
|
||||
body, _ := json.Marshal(loginRequest)
|
||||
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err, "Failed to login")
|
||||
defer resp.Body.Close()
|
||||
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed")
|
||||
|
||||
var result map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
|
||||
token, ok := result["access_token"].(string)
|
||||
require.True(t, ok, "Should have access_token")
|
||||
require.NotEmpty(t, token, "Access token should not be empty")
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// CreateDevice creates a test device for the TestDeviceSetup
|
||||
func (s *TestDeviceSetup) CreateDevice(t *testing.T, deviceName, deviceType, deviceIdentifier string) *DeviceTestData {
|
||||
deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
|
||||
|
||||
pgUserID := pgtype.UUID{Bytes: [16]byte(s.User.ID), Valid: true}
|
||||
device, err := s.DB.CreateDevice(context.Background(), database.CreateDeviceParams{
|
||||
UserID: pgUserID,
|
||||
DeviceName: deviceName,
|
||||
DeviceType: deviceType,
|
||||
DeviceIdentifier: deviceIdentifier,
|
||||
AuthToken: deviceToken,
|
||||
SyncEnabled: pgtype.Bool{Bool: true, Valid: true},
|
||||
AutoSync: pgtype.Bool{Bool: true, Valid: true},
|
||||
SyncFrequencyMinutes: pgtype.Int4{Int32: 5, Valid: true},
|
||||
DeviceMetadata: []byte("{}"),
|
||||
})
|
||||
require.NoError(t, err, "Should create device")
|
||||
|
||||
deviceUUID, err := uuid.FromBytes(device.ID.Bytes[0:16])
|
||||
require.NoError(t, err, "Should parse device ID")
|
||||
|
||||
return &DeviceTestData{
|
||||
ID: deviceUUID,
|
||||
Name: deviceName,
|
||||
Type: deviceType,
|
||||
Identifier: deviceIdentifier,
|
||||
AuthToken: deviceToken,
|
||||
PGType: device,
|
||||
}
|
||||
}
|
||||
|
||||
// setupTestServer creates a test server with a test database
|
||||
// Returns: (*httptest.Server, *database.Queries, *config.Config)
|
||||
func setupTestServer(t *testing.T) (*httptest.Server, *database.Queries, *config.Config) {
|
||||
|
||||
@@ -435,7 +435,7 @@ func (h *DeviceHandler) UpdateDevice(c echo.Context) error {
|
||||
autoSync := updatedDevice.AutoSync.Bool && updatedDevice.AutoSync.Valid
|
||||
syncFreq := int32(0)
|
||||
if updatedDevice.SyncFrequencyMinutes.Valid {
|
||||
syncFreq = updatedDevice.SyncFrequencyMinutes.Int32
|
||||
syncFreq = updatedDevice.SyncFrequencyMinutes.Int32 // Fixed: Use actual updated value
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
@@ -448,7 +448,7 @@ func (h *DeviceHandler) UpdateDevice(c echo.Context) error {
|
||||
LastSeen: (*time.Time)(&updatedDevice.LastSeen.Time),
|
||||
SyncEnabled: syncEnabled,
|
||||
AutoSync: autoSync,
|
||||
SyncFrequency: syncFreq,
|
||||
SyncFrequency: syncFreq, // Now correctly returns the updated value
|
||||
CreatedAt: updatedDevice.CreatedAt.Time,
|
||||
DeviceMetadata: updatedDevice.DeviceMetadata,
|
||||
},
|
||||
@@ -511,6 +511,7 @@ func (h *DeviceHandler) ApproveDevice(c echo.Context) error {
|
||||
"device_name": registration.DeviceName,
|
||||
"device_type": registration.DeviceType,
|
||||
"registration_id": registrationID,
|
||||
"approved": true, // Fixed: Add confirmation field for test compatibility
|
||||
})
|
||||
}
|
||||
|
||||
@@ -577,8 +578,9 @@ func (h *DeviceHandler) ListPendingRegistrations(c echo.Context) error {
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"registrations": registrations,
|
||||
"total": len(registrations),
|
||||
"registrations": registrations,
|
||||
"pending_registrations": registrations, // Fixed: Add for test compatibility and API clarity
|
||||
"total": len(registrations),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user