feat(mobile): identité user-first — login/password, SecureStore, scoping par compte
- api: DeviceRegistration device-only, login/changePassword/resolveUser, interception 401 (purge) exonérée sur le login ; type User aligné sur le fil (is_admin) - SecureStore (expo-secure-store v57) : token + profil, jamais en SQLite ; miroir active_user_id - AuthContext : statut loading/signedOut/signedIn, bootstrap register→restore, signIn/signOut, garde-fou switch de compte - app/login.tsx + gate de routes dans _layout (Redirect signedOut) ; i18n fr/en - DB v5 : user_id sur pending_operations + resource_permissions (UNIQUE par user), repos scopés (user_id IS ? OR IS NULL) - syncOutbox : delta permissions par compte (Map), outbox poussée du compte actif uniquement - tests: register/login/401/resolve/changePassword, scoping outbox+permissions, migrations v5 ; e2e live revert register→login admin
This commit is contained in:
@@ -13,10 +13,17 @@ import { MIGRATIONS, type MigrationDb } from '../services/db/migrations';
|
||||
import type { DbSession } from '../services/db/session';
|
||||
import { __setDbForTests } from '../services/db/session';
|
||||
import { api, setAuthToken } from '../api/client';
|
||||
import { enqueuePendingOperation, getPendingOperations } from '../services/db';
|
||||
import {
|
||||
clearActiveUserId,
|
||||
enqueuePendingOperation,
|
||||
getPendingOperations,
|
||||
setActiveUserId,
|
||||
} from '../services/db';
|
||||
import { pushPendingOps, refreshPermissions, resetPermissionCachedAtForTests } from '../features/syncOutbox';
|
||||
|
||||
const BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL ?? 'http://localhost:8080/api/v1';
|
||||
const ADMIN_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin';
|
||||
const ADMIN_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin';
|
||||
|
||||
type Harness = MigrationDb & DbSession;
|
||||
|
||||
@@ -75,9 +82,10 @@ beforeEach(async () => {
|
||||
afterEach(() => {
|
||||
__setDbForTests(null);
|
||||
setAuthToken(null);
|
||||
clearActiveUserId();
|
||||
});
|
||||
|
||||
test('bout en bout : register → push outbox → snapshot → relecture serveur', async (t) => {
|
||||
test('bout en bout : register + login admin → push outbox → snapshot → relecture serveur', async (t) => {
|
||||
try {
|
||||
await api.health();
|
||||
} catch (error) {
|
||||
@@ -88,11 +96,15 @@ test('bout en bout : register → push outbox → snapshot → relecture serveur
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Enregistrement du device (idempotent) puis login = SEULE porte de token.
|
||||
deviceId = newDeviceId();
|
||||
const reg = await api.registerDevice(deviceId);
|
||||
setAuthToken(reg.data.token);
|
||||
await api.registerDevice(deviceId);
|
||||
const login = await api.login(ADMIN_USERNAME, ADMIN_PASSWORD, deviceId);
|
||||
setAuthToken(login.data.token);
|
||||
await setActiveUserId(login.data.user.id);
|
||||
assert.equal(login.data.user.is_admin, true, 'l’admin E2E doit exister (ADMIN_*)');
|
||||
|
||||
// 1. Enqueue un CREATE_RESOURCE puis push
|
||||
// 1. Enqueue un CREATE_RESOURCE puis push (scopé au compte connecté)
|
||||
const name = `e2e-${deviceId.slice(0, 8)}`;
|
||||
const resourceId = newDeviceId();
|
||||
const opId = await enqueuePendingOperation({
|
||||
|
||||
Reference in New Issue
Block a user