Test file updates: - Update import paths in test files: bookmann/internal → bookhoard/internal - Update test helper functions and references - Update test comments and documentation This is part 6 of the project rename to Bookhoard.
390 lines
14 KiB
Go
390 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestDeviceRegistrationFlow(t *testing.T) {
|
|
ts, _, _, _ := setupTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// Step 1: Initiate device registration
|
|
regRequest := map[string]interface{}{
|
|
"device_name": "Test Kindle Paperwhite",
|
|
"device_type": "koreader",
|
|
"device_identifier": "kindle-test-hw-id-12345",
|
|
}
|
|
regBody, _ := json.Marshal(regRequest)
|
|
|
|
req := httptest.NewRequest("POST", "/api/devices/register", bytes.NewReader(regBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.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")
|
|
assert.NotEmpty(t, registrationID, "Registration ID should not be empty")
|
|
|
|
authURL, ok := regResponse["auth_url"].(string)
|
|
assert.True(t, ok, "Should have auth_url")
|
|
assert.NotEmpty(t, authURL, "Auth URL should not be empty")
|
|
|
|
// Step 2: Check registration status (should be pending initially)
|
|
statusRequest := map[string]interface{}{
|
|
"registration_id": registrationID,
|
|
}
|
|
statusBody, _ := json.Marshal(statusRequest)
|
|
|
|
req = httptest.NewRequest("POST", "/api/devices/register/status", bytes.NewReader(statusBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec = httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should check registration status")
|
|
|
|
var statusResponse map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &statusResponse)
|
|
|
|
status, ok := statusResponse["status"].(string)
|
|
assert.True(t, ok, "Should have status")
|
|
assert.Equal(t, "pending", status, "Should be pending initially")
|
|
|
|
// Step 3: Login as user to approve device
|
|
loginRequest := map[string]interface{}{
|
|
"login": "testuser@example.com",
|
|
"password": "testpass123",
|
|
}
|
|
loginBody, _ := json.Marshal(loginRequest)
|
|
|
|
req = httptest.NewRequest("POST", "/api/auth/login", bytes.NewReader(loginBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec = httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should login successfully")
|
|
|
|
var loginResponse map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &loginResponse)
|
|
|
|
token, ok := loginResponse["access_token"].(string)
|
|
assert.True(t, ok, "Should have access_token")
|
|
|
|
// Step 4: Approve the device
|
|
req = httptest.NewRequest("GET", fmt.Sprintf("/api/devices/approve/%s", registrationID), nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec = httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should approve device")
|
|
|
|
// Step 5: Check registration status again (should be approved now)
|
|
req = httptest.NewRequest("POST", "/api/devices/register/status", bytes.NewReader(statusBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec = httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should check registration status after approval")
|
|
|
|
var approvedStatus map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &approvedStatus)
|
|
|
|
status, ok = approvedStatus["status"].(string)
|
|
assert.True(t, ok, "Should have status")
|
|
assert.Equal(t, "approved", status, "Should be approved after user approval")
|
|
|
|
authToken, ok := approvedStatus["auth_token"].(string)
|
|
assert.True(t, ok, "Should have auth_token after approval")
|
|
assert.NotEmpty(t, authToken, "Auth token should not be empty")
|
|
}
|
|
|
|
func TestListDevices(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.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")
|
|
|
|
// List devices
|
|
req := httptest.NewRequest("GET", "/api/devices", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should list devices")
|
|
|
|
var response map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &response)
|
|
|
|
devices, ok := response["devices"].([]interface{})
|
|
assert.True(t, ok, "Should have devices array")
|
|
assert.GreaterOrEqual(t, len(devices), 1, "Should have at least one device")
|
|
|
|
firstDevice := devices[0].(map[string]interface{})
|
|
deviceName, ok := firstDevice["device_name"].(string)
|
|
assert.True(t, ok, "Should have device_name")
|
|
assert.Equal(t, "Test Device", deviceName, "Should match created device name")
|
|
}
|
|
|
|
func TestUpdateDevice(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.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")
|
|
|
|
// Update device
|
|
updateRequest := map[string]interface{}{
|
|
"device_name": "Updated Device Name",
|
|
"sync_enabled": false,
|
|
"sync_frequency_minutes": int32(10),
|
|
}
|
|
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.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should update device")
|
|
|
|
var response map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &response)
|
|
|
|
assert.True(t, response["device_updated"].(bool), "Should confirm device updated")
|
|
|
|
updatedDevice := response["device"].(map[string]interface{})
|
|
assert.Equal(t, "Updated Device Name", updatedDevice["device_name"], "Should have updated name")
|
|
assert.Equal(t, false, updatedDevice["sync_enabled"], "Should be disabled")
|
|
assert.Equal(t, int32(10), updatedDevice["sync_frequency"], "Should have updated frequency")
|
|
}
|
|
|
|
func TestDeleteDevice(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.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")
|
|
|
|
// Delete device
|
|
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/devices/%s", deviceID.String()), nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec := httptest.NewRecorder()
|
|
ts.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)
|
|
assert.Error(t, err, "Device should be deleted")
|
|
}
|
|
|
|
func TestDeviceAuthentication(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.Close()
|
|
|
|
// 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")
|
|
|
|
// Test device authentication
|
|
req := httptest.NewRequest("GET", "/api/devices", nil)
|
|
req.Header.Set("Authorization", "Bearer "+deviceToken)
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
// This should fail because device auth middleware is not applied to /api/devices
|
|
// Device auth is for sync endpoints only
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code, "Should require user auth for device management")
|
|
}
|
|
|
|
func TestListPendingRegistrations(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.Close()
|
|
|
|
token := loginTestUser(t, ts, db)
|
|
|
|
req := httptest.NewRequest("GET", "/api/devices/pending", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should list pending registrations")
|
|
|
|
var response map[string]interface{}
|
|
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")
|
|
}
|
|
|
|
func TestApproveDeviceRegistration(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.Close()
|
|
|
|
token := loginTestUser(t, ts, db)
|
|
|
|
regRequest := map[string]interface{}{
|
|
"device_name": "Test Device for Approval",
|
|
"device_type": "koreader",
|
|
"device_identifier": "test-approval-12345",
|
|
}
|
|
regBody, _ := json.Marshal(regRequest)
|
|
|
|
req := httptest.NewRequest("POST", "/api/devices/register", bytes.NewReader(regBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.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")
|
|
|
|
req = httptest.NewRequest("GET", fmt.Sprintf("/api/devices/approve/%s", registrationID), nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code, "Should approve device registration")
|
|
|
|
var approveResponse map[string]interface{}
|
|
json.Unmarshal(rec.Body.Bytes(), &approveResponse)
|
|
|
|
assert.True(t, approveResponse["approved"].(bool), "Should confirm approval")
|
|
}
|
|
|
|
func TestRejectDeviceRegistration(t *testing.T) {
|
|
ts, db, _, _ := setupTestServer(t)
|
|
defer ts.Close()
|
|
|
|
token := loginTestUser(t, ts, db)
|
|
|
|
regRequest := map[string]interface{}{
|
|
"device_name": "Test Device for Rejection",
|
|
"device_type": "koreader",
|
|
"device_identifier": "test-rejection-12345",
|
|
}
|
|
regBody, _ := json.Marshal(regRequest)
|
|
|
|
req := httptest.NewRequest("POST", "/api/devices/register", bytes.NewReader(regBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
ts.Config.Handler.ServeHTTP(rec, req)
|
|
|
|
assert.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")
|
|
|
|
req = httptest.NewRequest("POST", fmt.Sprintf("/api/devices/reject/%s", registrationID), nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
rec = httptest.NewRecorder()
|
|
ts.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.True(t, rejectResponse["rejected"].(bool), "Should confirm rejection")
|
|
}
|