vocabflow 0.0.1
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.
- package/dist/__tests__/api-client.test.d.ts +2 -0
- package/dist/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/__tests__/api-client.test.js +105 -0
- package/dist/__tests__/api-client.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +54 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/oauth-callback.test.d.ts +2 -0
- package/dist/__tests__/oauth-callback.test.d.ts.map +1 -0
- package/dist/__tests__/oauth-callback.test.js +46 -0
- package/dist/__tests__/oauth-callback.test.js.map +1 -0
- package/dist/__tests__/review.test.d.ts +2 -0
- package/dist/__tests__/review.test.d.ts.map +1 -0
- package/dist/__tests__/review.test.js +37 -0
- package/dist/__tests__/review.test.js.map +1 -0
- package/dist/__tests__/session.test.d.ts +2 -0
- package/dist/__tests__/session.test.d.ts.map +1 -0
- package/dist/__tests__/session.test.js +41 -0
- package/dist/__tests__/session.test.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +51 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +28 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +449 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +29 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +55 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +93 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/oauth-callback.d.ts +11 -0
- package/dist/lib/oauth-callback.d.ts.map +1 -0
- package/dist/lib/oauth-callback.js +83 -0
- package/dist/lib/oauth-callback.js.map +1 -0
- package/dist/lib/tty.d.ts +28 -0
- package/dist/lib/tty.d.ts.map +1 -0
- package/dist/lib/tty.js +59 -0
- package/dist/lib/tty.js.map +1 -0
- package/dist/types/review.d.ts +77 -0
- package/dist/types/review.d.ts.map +1 -0
- package/dist/types/review.js +2 -0
- package/dist/types/review.js.map +1 -0
- package/package.json +31 -0
- package/src/__tests__/api-client.test.ts +123 -0
- package/src/__tests__/config.test.ts +61 -0
- package/src/__tests__/oauth-callback.test.ts +54 -0
- package/src/__tests__/review.test.ts +41 -0
- package/src/__tests__/session.test.ts +53 -0
- package/src/commands/login.ts +60 -0
- package/src/commands/logout.ts +31 -0
- package/src/commands/review.ts +544 -0
- package/src/commands/status.ts +32 -0
- package/src/index.ts +45 -0
- package/src/lib/api-client.ts +126 -0
- package/src/lib/config.ts +53 -0
- package/src/lib/oauth-callback.ts +102 -0
- package/src/lib/tty.ts +64 -0
- package/src/types/review.ts +87 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +24 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/api-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const mockGetToken = vi.fn();
|
|
3
|
+
const mockGet = vi.fn();
|
|
4
|
+
const mockPost = vi.fn();
|
|
5
|
+
const mockPut = vi.fn();
|
|
6
|
+
const mockDelete = vi.fn();
|
|
7
|
+
const mockKyCreate = vi.fn();
|
|
8
|
+
vi.mock('../lib/config.js', () => ({
|
|
9
|
+
getToken: mockGetToken,
|
|
10
|
+
}));
|
|
11
|
+
vi.mock('ky', () => ({
|
|
12
|
+
default: {
|
|
13
|
+
create: mockKyCreate,
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
describe('CLI API client endpoint compatibility', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetModules();
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
mockGetToken.mockReturnValue('test-token');
|
|
21
|
+
mockGet.mockImplementation((_url) => ({
|
|
22
|
+
json: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
mockPost.mockImplementation((_url, _opts) => ({
|
|
25
|
+
json: vi.fn(),
|
|
26
|
+
}));
|
|
27
|
+
mockPut.mockImplementation((_url, _opts) => ({
|
|
28
|
+
json: vi.fn(),
|
|
29
|
+
}));
|
|
30
|
+
mockDelete.mockImplementation((_url) => ({
|
|
31
|
+
json: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
mockKyCreate.mockReturnValue({
|
|
34
|
+
get: mockGet,
|
|
35
|
+
post: mockPost,
|
|
36
|
+
put: mockPut,
|
|
37
|
+
delete: mockDelete,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('validateSession success should call relative endpoint without leading slash', async () => {
|
|
41
|
+
const jsonMock = vi.fn().mockResolvedValue({
|
|
42
|
+
code: 0,
|
|
43
|
+
message: 'ok',
|
|
44
|
+
data: { id: 'u1', email: 'test@example.com', name: 'Test User' },
|
|
45
|
+
});
|
|
46
|
+
mockGet.mockReturnValueOnce({ json: jsonMock });
|
|
47
|
+
const { validateSession } = await import('../lib/api-client.js');
|
|
48
|
+
const result = await validateSession();
|
|
49
|
+
expect(mockGet).toHaveBeenCalledWith('api/users/me');
|
|
50
|
+
expect(mockGet).not.toHaveBeenCalledWith('/api/users/me');
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
valid: true,
|
|
53
|
+
user: { id: 'u1', email: 'test@example.com', name: 'Test User' },
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it('isApiSuccess should accept both 0 and 200', async () => {
|
|
57
|
+
const { isApiSuccess } = await import('../lib/api-client.js');
|
|
58
|
+
expect(isApiSuccess(0)).toBe(true);
|
|
59
|
+
expect(isApiSuccess(200)).toBe(true);
|
|
60
|
+
expect(isApiSuccess(401)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
it('validateSession failure should return session-expired message for 401', async () => {
|
|
63
|
+
const jsonMock = vi.fn().mockRejectedValue({ response: { status: 401 } });
|
|
64
|
+
mockGet.mockReturnValueOnce({ json: jsonMock });
|
|
65
|
+
const { validateSession } = await import('../lib/api-client.js');
|
|
66
|
+
const result = await validateSession();
|
|
67
|
+
expect(mockGet).toHaveBeenCalledWith('api/users/me');
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
valid: false,
|
|
70
|
+
error: 'Session expired. Please run `vocab login`.',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
it('reviewApi should keep all endpoints relative to prefixUrl', async () => {
|
|
74
|
+
const getJsonMock = vi.fn().mockResolvedValue({ code: 200, data: {} });
|
|
75
|
+
const postJsonMock = vi.fn().mockResolvedValue({ code: 200, data: {} });
|
|
76
|
+
const deleteJsonMock = vi.fn().mockResolvedValue({ code: 200, data: {} });
|
|
77
|
+
mockGet.mockReturnValue({ json: getJsonMock });
|
|
78
|
+
mockPost.mockReturnValue({ json: postJsonMock });
|
|
79
|
+
mockDelete.mockReturnValue({ json: deleteJsonMock });
|
|
80
|
+
const { reviewApi } = await import('../lib/api-client.js');
|
|
81
|
+
await reviewApi.getTodayReview();
|
|
82
|
+
await reviewApi.getReviewQuestion(123);
|
|
83
|
+
await reviewApi.submitReview({
|
|
84
|
+
wordId: 123,
|
|
85
|
+
result: 'KNOWN',
|
|
86
|
+
mode: 'FLASHCARD_FORWARD',
|
|
87
|
+
});
|
|
88
|
+
await reviewApi.deleteWord(123);
|
|
89
|
+
expect(mockGet).toHaveBeenCalledWith('api/review/today');
|
|
90
|
+
expect(mockGet).toHaveBeenCalledWith('api/review/questions/123?mode=FLASHCARD_FORWARD');
|
|
91
|
+
expect(mockPost).toHaveBeenCalledWith('api/review/complete', expect.objectContaining({
|
|
92
|
+
json: {
|
|
93
|
+
wordId: 123,
|
|
94
|
+
result: 'KNOWN',
|
|
95
|
+
mode: 'FLASHCARD_FORWARD',
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
expect(mockDelete).toHaveBeenCalledWith('api/words/123');
|
|
99
|
+
expect(mockGet).not.toHaveBeenCalledWith('/api/review/today');
|
|
100
|
+
expect(mockGet).not.toHaveBeenCalledWith('/api/review/questions/123?mode=FLASHCARD_FORWARD');
|
|
101
|
+
expect(mockPost).not.toHaveBeenCalledWith('/api/review/complete', expect.anything());
|
|
102
|
+
expect(mockDelete).not.toHaveBeenCalledWith('/api/words/123');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=api-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.test.js","sourceRoot":"","sources":["../../src/__tests__/api-client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
// Mock the conf module before importing config
|
|
3
|
+
vi.mock('conf', () => {
|
|
4
|
+
return {
|
|
5
|
+
default: vi.fn((options) => {
|
|
6
|
+
const defaults = options?.defaults ?? {};
|
|
7
|
+
const store = { ...defaults };
|
|
8
|
+
return {
|
|
9
|
+
get: (key) => store[key],
|
|
10
|
+
set: (key, value) => { store[key] = value; },
|
|
11
|
+
delete: (key) => { delete store[key]; },
|
|
12
|
+
has: (key) => key in store,
|
|
13
|
+
clear: () => { Object.keys(store).forEach(k => delete store[k]); },
|
|
14
|
+
};
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
describe('config store', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Reset mock state
|
|
21
|
+
vi.resetModules();
|
|
22
|
+
});
|
|
23
|
+
it('should return null for token when not set', async () => {
|
|
24
|
+
const { getToken } = await import('../lib/config.js');
|
|
25
|
+
expect(getToken()).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
it('should store and retrieve token via setAuth', async () => {
|
|
28
|
+
const { setAuth, getToken } = await import('../lib/config.js');
|
|
29
|
+
setAuth({ token: 'test-token', email: 'test@example.com', expiresAt: Date.now() + 86400000 });
|
|
30
|
+
expect(getToken()).toBe('test-token');
|
|
31
|
+
});
|
|
32
|
+
it('should clear all auth data via clearAuth', async () => {
|
|
33
|
+
const { setAuth, clearAuth, hasToken } = await import('../lib/config.js');
|
|
34
|
+
setAuth({ token: 'test-token', email: 'test@example.com', expiresAt: Date.now() + 86400000 });
|
|
35
|
+
clearAuth();
|
|
36
|
+
expect(hasToken()).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it('should detect expired tokens', async () => {
|
|
39
|
+
const { setAuth, isTokenExpired } = await import('../lib/config.js');
|
|
40
|
+
setAuth({ token: 'test-token', email: 'test@example.com', expiresAt: Date.now() - 1000 });
|
|
41
|
+
expect(isTokenExpired()).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('should detect non-expired tokens', async () => {
|
|
44
|
+
const { setAuth, isTokenExpired } = await import('../lib/config.js');
|
|
45
|
+
setAuth({ token: 'test-token', email: 'test@example.com', expiresAt: Date.now() + 86400000 });
|
|
46
|
+
expect(isTokenExpired()).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
it('should return stored email', async () => {
|
|
49
|
+
const { setAuth, getEmail } = await import('../lib/config.js');
|
|
50
|
+
setAuth({ token: 'test-token', email: 'test@example.com', expiresAt: Date.now() + 86400000 });
|
|
51
|
+
expect(getEmail()).toBe('test@example.com');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-callback.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/oauth-callback.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
describe('OAuth callback server', () => {
|
|
4
|
+
it('should export startCallbackServer function', async () => {
|
|
5
|
+
const { startCallbackServer } = await import('../lib/oauth-callback.js');
|
|
6
|
+
expect(typeof startCallbackServer).toBe('function');
|
|
7
|
+
});
|
|
8
|
+
it('should expose CallbackResult type shape', async () => {
|
|
9
|
+
const success = { type: 'success', token: 't' };
|
|
10
|
+
const error = { type: 'error', message: 'm' };
|
|
11
|
+
expect(success.type).toBe('success');
|
|
12
|
+
expect(error.type).toBe('error');
|
|
13
|
+
});
|
|
14
|
+
it('should resolve with success token when callback receives ?token=xxx', async () => {
|
|
15
|
+
const { startCallbackServer } = await import('../lib/oauth-callback.js');
|
|
16
|
+
const promise = startCallbackServer();
|
|
17
|
+
// Wait for server to be ready, then send the token callback
|
|
18
|
+
await new Promise((resolve, reject) => {
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
http.get('http://localhost:58421/?token=test-token-abc', (res) => {
|
|
21
|
+
res.resume();
|
|
22
|
+
res.on('end', resolve);
|
|
23
|
+
}).on('error', reject);
|
|
24
|
+
}, 50);
|
|
25
|
+
});
|
|
26
|
+
const result = await promise;
|
|
27
|
+
expect(result.type).toBe('success');
|
|
28
|
+
expect(result.token).toBe('test-token-abc');
|
|
29
|
+
});
|
|
30
|
+
it('should resolve with error when callback receives ?error=xxx', async () => {
|
|
31
|
+
const { startCallbackServer } = await import('../lib/oauth-callback.js');
|
|
32
|
+
const promise = startCallbackServer();
|
|
33
|
+
await new Promise((resolve, reject) => {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
http.get('http://localhost:58421/?error=auth_failed&error_description=User+cancelled', (res) => {
|
|
36
|
+
res.resume();
|
|
37
|
+
res.on('end', resolve);
|
|
38
|
+
}).on('error', reject);
|
|
39
|
+
}, 50);
|
|
40
|
+
});
|
|
41
|
+
const result = await promise;
|
|
42
|
+
expect(result.type).toBe('error');
|
|
43
|
+
expect(result.message).toBe('User cancelled');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=oauth-callback.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-callback.test.js","sourceRoot":"","sources":["../../src/__tests__/oauth-callback.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/review.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
// Mock the dependencies
|
|
3
|
+
vi.mock('../lib/api-client.js', () => ({
|
|
4
|
+
requireAuth: vi.fn(),
|
|
5
|
+
reviewApi: {
|
|
6
|
+
getTodayReview: vi.fn(),
|
|
7
|
+
getReviewQuestion: vi.fn(),
|
|
8
|
+
submitReview: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
vi.mock('../lib/tty.js', () => ({
|
|
12
|
+
isInteractive: vi.fn(),
|
|
13
|
+
setupRawMode: vi.fn(),
|
|
14
|
+
cleanup: vi.fn(),
|
|
15
|
+
clearScreen: vi.fn(),
|
|
16
|
+
moveCursorTop: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
describe('review command', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
describe('non-interactive mode', () => {
|
|
23
|
+
it('should output words in plain text format');
|
|
24
|
+
it('should exit with code 0');
|
|
25
|
+
it('should handle API errors gracefully');
|
|
26
|
+
});
|
|
27
|
+
describe('interactive mode', () => {
|
|
28
|
+
it('should require authentication');
|
|
29
|
+
it('should render word display');
|
|
30
|
+
it('should fetch question on reveal (getReviewQuestion)');
|
|
31
|
+
it('should display meanings and examples when revealed');
|
|
32
|
+
it('should handle keyboard navigation');
|
|
33
|
+
it('should handle rating actions');
|
|
34
|
+
it('should show completion message');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=review.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.test.js","sourceRoot":"","sources":["../../src/__tests__/review.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEtD,wBAAwB;AACxB,EAAE,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;IACrC,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE;IACpB,SAAS,EAAE;QACT,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;QACvB,iBAAiB,EAAE,EAAE,CAAC,EAAE,EAAE;QAC1B,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;KACtB;CACF,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;IACtB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;IACrB,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;IAChB,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE;IACpB,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;CACvB,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,EAAE,CAAC,0CAA0C,CAAC,CAAC;QAC/C,EAAE,CAAC,yBAAyB,CAAC,CAAC;QAC9B,EAAE,CAAC,qCAAqC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,+BAA+B,CAAC,CAAC;QACpC,EAAE,CAAC,4BAA4B,CAAC,CAAC;QACjC,EAAE,CAAC,qDAAqD,CAAC,CAAC;QAC1D,EAAE,CAAC,oDAAoD,CAAC,CAAC;QACzD,EAAE,CAAC,mCAAmC,CAAC,CAAC;QACxC,EAAE,CAAC,8BAA8B,CAAC,CAAC;QACnC,EAAE,CAAC,gCAAgC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
describe('session validation', () => {
|
|
3
|
+
it('should return valid:true for successful session', async () => {
|
|
4
|
+
// Mock successful /api/users/me response
|
|
5
|
+
const mockFetch = vi.fn(() => Promise.resolve(new Response(JSON.stringify({ code: 0, message: 'success', data: { id: 1, email: 'test@example.com' } }), {
|
|
6
|
+
status: 200,
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
})));
|
|
9
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
10
|
+
const { validateSession } = await import('../lib/api-client.js');
|
|
11
|
+
const result = await validateSession();
|
|
12
|
+
expect(result.valid).toBe(true);
|
|
13
|
+
expect(result.user).toBeDefined();
|
|
14
|
+
expect(result.user?.email).toBe('test@example.com');
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
it('should return valid:false for expired session', async () => {
|
|
18
|
+
// Mock 401 response
|
|
19
|
+
const mockFetch = vi.fn(() => Promise.resolve(new Response(JSON.stringify({ code: 401, message: 'Unauthorized' }), {
|
|
20
|
+
status: 401,
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
})));
|
|
23
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
24
|
+
const { validateSession } = await import('../lib/api-client.js');
|
|
25
|
+
const result = await validateSession();
|
|
26
|
+
expect(result.valid).toBe(false);
|
|
27
|
+
expect(result.error).toBe('Session expired. Please run `vocab login`.');
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
});
|
|
30
|
+
it('should handle network errors gracefully', async () => {
|
|
31
|
+
// Mock connection refused
|
|
32
|
+
const mockFetch = vi.fn(() => Promise.reject(new TypeError('fetch failed')));
|
|
33
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
34
|
+
const { validateSession } = await import('../lib/api-client.js');
|
|
35
|
+
const result = await validateSession();
|
|
36
|
+
expect(result.valid).toBe(false);
|
|
37
|
+
expect(result.error).toBe('Unable to connect. Check your network.');
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.test.js","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAQA,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAmD3C"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
import picocolors from 'picocolors';
|
|
3
|
+
import { startCallbackServer } from '../lib/oauth-callback.js';
|
|
4
|
+
import { setAuth } from '../lib/config.js';
|
|
5
|
+
import { cliApi, WEBAPP_URL, isApiSuccess } from '../lib/api-client.js';
|
|
6
|
+
const CALLBACK_URI = 'http://localhost:58421';
|
|
7
|
+
export async function login() {
|
|
8
|
+
console.log(picocolors.cyan('Opening browser to authenticate...'));
|
|
9
|
+
// Start callback server
|
|
10
|
+
const callbackPromise = startCallbackServer();
|
|
11
|
+
try {
|
|
12
|
+
// Open webapp login page with callback URL
|
|
13
|
+
const loginUrl = `${WEBAPP_URL}/login?callback=${encodeURIComponent(CALLBACK_URI)}`;
|
|
14
|
+
await open(loginUrl);
|
|
15
|
+
// Wait for callback
|
|
16
|
+
const result = await callbackPromise;
|
|
17
|
+
if (result.type === 'error') {
|
|
18
|
+
console.error(picocolors.red(`Authentication failed: ${result.message}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// FIXED: Fetch user profile to get email, then store with setAuth
|
|
22
|
+
let email = '';
|
|
23
|
+
try {
|
|
24
|
+
// prefixUrl requires relative path without leading slash
|
|
25
|
+
const userResponse = await cliApi.get('api/users/me');
|
|
26
|
+
if (isApiSuccess(userResponse.code) && userResponse.data?.email) {
|
|
27
|
+
email = userResponse.data.email;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
// Non-fatal: we still have the token, email just won't be stored
|
|
32
|
+
console.log(picocolors.dim('Warning: Could not fetch user email. Continuing without email.'));
|
|
33
|
+
}
|
|
34
|
+
// Store token with email (email may be empty string if fetch failed)
|
|
35
|
+
setAuth({
|
|
36
|
+
token: result.token,
|
|
37
|
+
email: email,
|
|
38
|
+
expiresAt: null, // No refresh token, validate via API
|
|
39
|
+
});
|
|
40
|
+
console.log(picocolors.green('Authentication successful!'));
|
|
41
|
+
if (email) {
|
|
42
|
+
console.log(picocolors.dim(`Logged in as ${email}`));
|
|
43
|
+
}
|
|
44
|
+
console.log(picocolors.dim('Run `vocab status` to verify your session.'));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.error(picocolors.red(`Login failed: ${err.message}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC,CAAC;IAEnE,wBAAwB;IACxB,MAAM,eAAe,GAAG,mBAAmB,EAAE,CAAC;IAE9C,IAAI,CAAC;QACH,2CAA2C;QAC3C,MAAM,QAAQ,GAAG,GAAG,UAAU,mBAAmB,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErB,oBAAoB;QACpB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC;QAErC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,0BAA0B,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,kEAAkE;QAClE,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,IAAI,CAAC;YACH,yDAAyD;YACzD,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,GAAG,CACnC,cAAc,CACf,CAAC;YACF,IAAI,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;gBAChE,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iEAAiE;YACjE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC,CAAC;QAChG,CAAC;QAED,qEAAqE;QACrE,OAAO,CAAC;YACN,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,KAAK;YACZ,SAAS,EAAE,IAAI,EAAE,qCAAqC;SACvD,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;QAC5D,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,gBAAgB,KAAK,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC,CAAC;IAE5E,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,iBAAiB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CA2B5C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import picocolors from 'picocolors';
|
|
2
|
+
import { clearAuth, hasToken, getEmail } from '../lib/config.js';
|
|
3
|
+
export async function logout() {
|
|
4
|
+
if (!hasToken()) {
|
|
5
|
+
console.log(picocolors.yellow('You are not logged in.'));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const email = getEmail();
|
|
9
|
+
const emailMsg = email ? ` for ${email}` : '';
|
|
10
|
+
// Read confirmation from stdin
|
|
11
|
+
process.stdout.write(picocolors.yellow(`Clear local credentials${emailMsg}? [y/N] `));
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
process.stdin.once('data', (data) => {
|
|
14
|
+
const input = data.toString().trim().toLowerCase();
|
|
15
|
+
console.log(); // New line after input
|
|
16
|
+
if (input === 'y' || input === 'yes') {
|
|
17
|
+
clearAuth();
|
|
18
|
+
console.log(picocolors.green('Logged out successfully.'));
|
|
19
|
+
resolve();
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log(picocolors.dim('Logout cancelled.'));
|
|
23
|
+
resolve();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjE,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9C,+BAA+B;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,0BAA0B,QAAQ,UAAU,CAAC,CAAC,CAAC;IAEtF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACnD,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,uBAAuB;YAEtC,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;gBACrC,SAAS,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;gBAC1D,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACjD,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAkRA,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CA6Q5C"}
|