@tenxyte/core 0.1.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,95 +1,95 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { RbacModule } from '../../src/modules/rbac';
3
- import { TenxyteHttpClient } from '../../src/http/client';
4
-
5
- // Helper to create a dummy JWT for testing
6
- function createDummyJwt(payload: any) {
7
- const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64');
8
- return `header.${encodedPayload}.signature`;
9
- }
10
-
11
- describe('RbacModule', () => {
12
- let client: TenxyteHttpClient;
13
- let rbac: RbacModule;
14
-
15
- beforeEach(() => {
16
- client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
17
- rbac = new RbacModule(client);
18
- vi.spyOn(client, 'request').mockImplementation(async () => {
19
- return {};
20
- });
21
- });
22
-
23
- describe('Synchronous Decoding & Checks', () => {
24
- const token = createDummyJwt({
25
- roles: ['admin', 'manager'],
26
- permissions: ['users.view', 'users.edit']
27
- });
28
-
29
- it('hasRole should return true if role exists', () => {
30
- expect(rbac.hasRole('admin', token)).toBe(true);
31
- expect(rbac.hasRole('user', token)).toBe(false);
32
- });
33
-
34
- it('hasAnyRole should return true if any role exists', () => {
35
- expect(rbac.hasAnyRole(['admin', 'user'], token)).toBe(true);
36
- expect(rbac.hasAnyRole(['user', 'guest'], token)).toBe(false);
37
- });
38
-
39
- it('hasAllRoles should return true if all roles exist', () => {
40
- expect(rbac.hasAllRoles(['admin', 'manager'], token)).toBe(true);
41
- expect(rbac.hasAllRoles(['admin', 'user'], token)).toBe(false);
42
- });
43
-
44
- it('hasPermission should return true if permission exists', () => {
45
- expect(rbac.hasPermission('users.view', token)).toBe(true);
46
- expect(rbac.hasPermission('users.delete', token)).toBe(false);
47
- });
48
-
49
- it('setToken should cache the token for parameter-less calls', () => {
50
- rbac.setToken(token);
51
- expect(rbac.hasRole('admin')).toBe(true);
52
- expect(rbac.hasPermission('users.edit')).toBe(true);
53
-
54
- rbac.setToken(null);
55
- expect(rbac.hasRole('admin')).toBe(false);
56
- });
57
- });
58
-
59
- describe('Roles CRUD & Permissions', () => {
60
- it('listRoles should GET /api/v1/auth/roles/', async () => {
61
- vi.mocked(client.request).mockResolvedValueOnce([{ id: '1', name: 'admin' }]);
62
- const result = await rbac.listRoles();
63
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', { method: 'GET' });
64
- expect(result.length).toBe(1);
65
- });
66
-
67
- it('createRole should POST /api/v1/auth/roles/', async () => {
68
- vi.mocked(client.request).mockResolvedValueOnce({ id: '2', name: 'newRole' });
69
- const data = { name: 'newRole', permission_codes: ['a.b'] };
70
- await rbac.createRole(data);
71
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', {
72
- method: 'POST',
73
- body: data,
74
- });
75
- });
76
-
77
- it('assignRoleToUser should POST to users/{id}/roles/', async () => {
78
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
79
- await rbac.assignRoleToUser('user123', 'admin');
80
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/users/user123/roles/', {
81
- method: 'POST',
82
- body: { role_code: 'admin' },
83
- });
84
- });
85
-
86
- it('removePermissionsFromRole should DELETE with body payload if config allows', async () => {
87
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
88
- await rbac.removePermissionsFromRole('role123', ['users.view']);
89
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/role123/permissions/', {
90
- method: 'DELETE',
91
- body: { permission_codes: ['users.view'] },
92
- } as any);
93
- });
94
- });
95
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { RbacModule } from '../../src/modules/rbac';
3
+ import { TenxyteHttpClient } from '../../src/http/client';
4
+
5
+ // Helper to create a dummy JWT for testing
6
+ function createDummyJwt(payload: any) {
7
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64');
8
+ return `header.${encodedPayload}.signature`;
9
+ }
10
+
11
+ describe('RbacModule', () => {
12
+ let client: TenxyteHttpClient;
13
+ let rbac: RbacModule;
14
+
15
+ beforeEach(() => {
16
+ client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
17
+ rbac = new RbacModule(client);
18
+ vi.spyOn(client, 'request').mockImplementation(async () => {
19
+ return {};
20
+ });
21
+ });
22
+
23
+ describe('Synchronous Decoding & Checks', () => {
24
+ const token = createDummyJwt({
25
+ roles: ['admin', 'manager'],
26
+ permissions: ['users.view', 'users.edit']
27
+ });
28
+
29
+ it('hasRole should return true if role exists', () => {
30
+ expect(rbac.hasRole('admin', token)).toBe(true);
31
+ expect(rbac.hasRole('user', token)).toBe(false);
32
+ });
33
+
34
+ it('hasAnyRole should return true if any role exists', () => {
35
+ expect(rbac.hasAnyRole(['admin', 'user'], token)).toBe(true);
36
+ expect(rbac.hasAnyRole(['user', 'guest'], token)).toBe(false);
37
+ });
38
+
39
+ it('hasAllRoles should return true if all roles exist', () => {
40
+ expect(rbac.hasAllRoles(['admin', 'manager'], token)).toBe(true);
41
+ expect(rbac.hasAllRoles(['admin', 'user'], token)).toBe(false);
42
+ });
43
+
44
+ it('hasPermission should return true if permission exists', () => {
45
+ expect(rbac.hasPermission('users.view', token)).toBe(true);
46
+ expect(rbac.hasPermission('users.delete', token)).toBe(false);
47
+ });
48
+
49
+ it('setToken should cache the token for parameter-less calls', () => {
50
+ rbac.setToken(token);
51
+ expect(rbac.hasRole('admin')).toBe(true);
52
+ expect(rbac.hasPermission('users.edit')).toBe(true);
53
+
54
+ rbac.setToken(null);
55
+ expect(rbac.hasRole('admin')).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('Roles CRUD & Permissions', () => {
60
+ it('listRoles should GET /api/v1/auth/roles/', async () => {
61
+ vi.mocked(client.request).mockResolvedValueOnce([{ id: '1', name: 'admin' }]);
62
+ const result = await rbac.listRoles();
63
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', { method: 'GET' });
64
+ expect(result.length).toBe(1);
65
+ });
66
+
67
+ it('createRole should POST /api/v1/auth/roles/', async () => {
68
+ vi.mocked(client.request).mockResolvedValueOnce({ id: '2', name: 'newRole' });
69
+ const data = { name: 'newRole', permission_codes: ['a.b'] };
70
+ await rbac.createRole(data);
71
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', {
72
+ method: 'POST',
73
+ body: data,
74
+ });
75
+ });
76
+
77
+ it('assignRoleToUser should POST to users/{id}/roles/', async () => {
78
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
79
+ await rbac.assignRoleToUser('user123', 'admin');
80
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/users/user123/roles/', {
81
+ method: 'POST',
82
+ body: { role_code: 'admin' },
83
+ });
84
+ });
85
+
86
+ it('removePermissionsFromRole should DELETE with body payload if config allows', async () => {
87
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
88
+ await rbac.removePermissionsFromRole('role123', ['users.view']);
89
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/role123/permissions/', {
90
+ method: 'DELETE',
91
+ body: { permission_codes: ['users.view'] },
92
+ } as any);
93
+ });
94
+ });
95
+ });
@@ -1,75 +1,85 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { SecurityModule } from '../../src/modules/security';
3
- import { TenxyteHttpClient } from '../../src/http/client';
4
-
5
- describe('SecurityModule', () => {
6
- let client: TenxyteHttpClient;
7
- let security: SecurityModule;
8
-
9
- beforeEach(() => {
10
- client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
11
- security = new SecurityModule(client);
12
- vi.spyOn(client, 'request').mockImplementation(async () => {
13
- return {};
14
- });
15
- });
16
-
17
- it('requestOtp should POST to /api/v1/auth/otp/request/', async () => {
18
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
19
- const data = { email: 'test@example.com', type: 'email_verification' as const };
20
- await security.requestOtp(data);
21
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/otp/request/', {
22
- method: 'POST',
23
- body: data,
24
- });
25
- });
26
-
27
- it('setup2FA should POST to /api/v1/auth/2fa/setup/', async () => {
28
- const mockResponse = { secret: 'secret_key', qr_code_url: 'url', backup_codes: ['123'] };
29
- vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
30
-
31
- const result = await security.setup2FA();
32
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/setup/', {
33
- method: 'POST',
34
- body: undefined
35
- });
36
- expect(result).toEqual(mockResponse);
37
- });
38
-
39
- it('confirm2FA should POST to /api/v1/auth/2fa/confirm/', async () => {
40
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
41
- await security.confirm2FA('123456');
42
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/confirm/', {
43
- method: 'POST',
44
- body: { totp_code: '123456' },
45
- });
46
- });
47
-
48
- it('resetPasswordRequest should POST to /api/v1/auth/password/reset/request/', async () => {
49
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
50
- await security.resetPasswordRequest({ email: 'hello@example.com' });
51
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/password/reset/request/', {
52
- method: 'POST',
53
- body: { email: 'hello@example.com' },
54
- });
55
- });
56
-
57
- it('authenticateWebAuthnBegin should POST to /api/v1/auth/webauthn/authenticate/begin/', async () => {
58
- const mockResponse = { publicKey: {} };
59
- vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
60
- const result = await security.authenticateWebAuthnBegin({ email: 'user@example.com' });
61
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/authenticate/begin/', {
62
- method: 'POST',
63
- body: { email: 'user@example.com' },
64
- });
65
- expect(result).toEqual(mockResponse);
66
- });
67
-
68
- it('deleteWebAuthnCredential should DELETE /api/v1/auth/webauthn/credentials/{credentialId}/', async () => {
69
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
70
- await security.deleteWebAuthnCredential('cred_123');
71
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/credentials/cred_123/', {
72
- method: 'DELETE',
73
- });
74
- });
75
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { SecurityModule } from '../../src/modules/security';
3
+ import { TenxyteHttpClient } from '../../src/http/client';
4
+
5
+ describe('SecurityModule', () => {
6
+ let client: TenxyteHttpClient;
7
+ let security: SecurityModule;
8
+
9
+ beforeEach(() => {
10
+ client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
11
+ security = new SecurityModule(client);
12
+ vi.spyOn(client, 'request').mockImplementation(async () => {
13
+ return {};
14
+ });
15
+ });
16
+
17
+ it('requestOtp should POST to /api/v1/auth/otp/request/', async () => {
18
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
19
+ await security.requestOtp('email');
20
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/otp/request/', {
21
+ method: 'POST',
22
+ body: { type: 'email_verification' },
23
+ });
24
+ });
25
+
26
+ it('setup2FA should POST to /api/v1/auth/2fa/setup/', async () => {
27
+ const mockResponse = { secret: 'secret_key', qr_code_url: 'url', backup_codes: ['123'] };
28
+ vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
29
+
30
+ const result = await security.setup2FA();
31
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/setup/', {
32
+ method: 'POST',
33
+ body: undefined
34
+ });
35
+ expect(result).toEqual(mockResponse);
36
+ });
37
+
38
+ it('confirm2FA should POST to /api/v1/auth/2fa/confirm/', async () => {
39
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
40
+ await security.confirm2FA('123456');
41
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/confirm/', {
42
+ method: 'POST',
43
+ body: { totp_code: '123456' },
44
+ });
45
+ });
46
+
47
+ it('resetPasswordRequest should POST to /api/v1/auth/password/reset/request/', async () => {
48
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
49
+ await security.resetPasswordRequest({ email: 'hello@example.com' });
50
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/password/reset/request/', {
51
+ method: 'POST',
52
+ body: { email: 'hello@example.com' },
53
+ });
54
+ });
55
+
56
+ it('authenticateWebAuthn should POST to /api/v1/auth/webauthn/authenticate/begin/', async () => {
57
+ const mockResponse = { publicKey: {} };
58
+ vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
59
+ // We mock navigator to stop the promise from throwing regarding missing credentials API
60
+ vi.stubGlobal('navigator', {
61
+ credentials: {
62
+ get: vi.fn(),
63
+ }
64
+ });
65
+
66
+ try {
67
+ await security.authenticateWebAuthn('user@example.com');
68
+ } catch (e) { /* expected to throw because navigator mock returns undefined */ }
69
+
70
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/authenticate/begin/', {
71
+ method: 'POST',
72
+ body: { email: 'user@example.com' },
73
+ });
74
+
75
+ vi.unstubAllGlobals();
76
+ });
77
+
78
+ it('deleteWebAuthnCredential should DELETE /api/v1/auth/webauthn/credentials/{credentialId}/', async () => {
79
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
80
+ await security.deleteWebAuthnCredential(123);
81
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/credentials/123/', {
82
+ method: 'DELETE',
83
+ });
84
+ });
85
+ });
@@ -1,76 +1,76 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { UserModule } from '../../src/modules/user';
3
- import { TenxyteHttpClient } from '../../src/http/client';
4
-
5
- describe('UserModule', () => {
6
- let client: TenxyteHttpClient;
7
- let user: UserModule;
8
-
9
- beforeEach(() => {
10
- client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
11
- user = new UserModule(client);
12
- vi.spyOn(client, 'request').mockImplementation(async () => {
13
- return {};
14
- });
15
- });
16
-
17
- // --- Profile ---
18
- it('getProfile should GET /api/v1/auth/me/', async () => {
19
- vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
20
- const result = await user.getProfile();
21
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', { method: 'GET' });
22
- expect(result.id).toBe('me');
23
- });
24
-
25
- it('updateProfile should PATCH /api/v1/auth/me/', async () => {
26
- vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
27
- const data = { first_name: 'John' };
28
- await user.updateProfile(data);
29
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
30
- method: 'PATCH',
31
- body: data,
32
- });
33
- });
34
-
35
- it('uploadAvatar should PATCH /api/v1/auth/me/ with FormData', async () => {
36
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
37
-
38
- // Mock global FormData if not in purely browser environment
39
- const FormDataMock = class { append() { } };
40
- const formData = new FormDataMock() as any;
41
-
42
- await user.uploadAvatar(formData);
43
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
44
- method: 'PATCH',
45
- body: formData,
46
- });
47
- });
48
-
49
- it('deleteAccount should POST to /api/v1/auth/request-account-deletion/', async () => {
50
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
51
- await user.deleteAccount('mypassword', '123456');
52
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/request-account-deletion/', {
53
- method: 'POST',
54
- body: { password: 'mypassword', otp_code: '123456' },
55
- });
56
- });
57
-
58
- // --- Admin Actions ---
59
- it('listUsers should GET /api/v1/auth/admin/users/', async () => {
60
- vi.mocked(client.request).mockResolvedValueOnce([]);
61
- await user.listUsers({ page: 1 });
62
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/', {
63
- method: 'GET',
64
- params: { page: 1 },
65
- });
66
- });
67
-
68
- it('banUser should POST /api/v1/auth/admin/users/{id}/ban/', async () => {
69
- vi.mocked(client.request).mockResolvedValueOnce(undefined);
70
- await user.banUser('user-1', 'spam');
71
- expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/user-1/ban/', {
72
- method: 'POST',
73
- body: { reason: 'spam' },
74
- });
75
- });
76
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { UserModule } from '../../src/modules/user';
3
+ import { TenxyteHttpClient } from '../../src/http/client';
4
+
5
+ describe('UserModule', () => {
6
+ let client: TenxyteHttpClient;
7
+ let user: UserModule;
8
+
9
+ beforeEach(() => {
10
+ client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
11
+ user = new UserModule(client);
12
+ vi.spyOn(client, 'request').mockImplementation(async () => {
13
+ return {};
14
+ });
15
+ });
16
+
17
+ // --- Profile ---
18
+ it('getProfile should GET /api/v1/auth/me/', async () => {
19
+ vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
20
+ const result = await user.getProfile();
21
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', { method: 'GET' });
22
+ expect(result.id).toBe('me');
23
+ });
24
+
25
+ it('updateProfile should PATCH /api/v1/auth/me/', async () => {
26
+ vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
27
+ const data = { first_name: 'John' };
28
+ await user.updateProfile(data);
29
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
30
+ method: 'PATCH',
31
+ body: data,
32
+ });
33
+ });
34
+
35
+ it('uploadAvatar should PATCH /api/v1/auth/me/ with FormData', async () => {
36
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
37
+
38
+ // Mock global FormData if not in purely browser environment
39
+ const FormDataMock = class { append() { } };
40
+ const formData = new FormDataMock() as any;
41
+
42
+ await user.uploadAvatar(formData);
43
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
44
+ method: 'PATCH',
45
+ body: formData,
46
+ });
47
+ });
48
+
49
+ it('deleteAccount should POST to /api/v1/auth/request-account-deletion/', async () => {
50
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
51
+ await user.deleteAccount('mypassword', '123456');
52
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/request-account-deletion/', {
53
+ method: 'POST',
54
+ body: { password: 'mypassword', otp_code: '123456' },
55
+ });
56
+ });
57
+
58
+ // --- Admin Actions ---
59
+ it('listUsers should GET /api/v1/auth/admin/users/', async () => {
60
+ vi.mocked(client.request).mockResolvedValueOnce([]);
61
+ await user.listUsers({ page: 1 });
62
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/', {
63
+ method: 'GET',
64
+ params: { page: 1 },
65
+ });
66
+ });
67
+
68
+ it('banUser should POST /api/v1/auth/admin/users/{id}/ban/', async () => {
69
+ vi.mocked(client.request).mockResolvedValueOnce(undefined);
70
+ await user.banUser('user-1', 'spam');
71
+ expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/user-1/ban/', {
72
+ method: 'POST',
73
+ body: { reason: 'spam' },
74
+ });
75
+ });
76
+ });