@indigoai-us/hq-cli 5.1.0 → 5.1.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/index.js +1 -1
- package/dist/utils/api-client.js +1 -1
- package/package.json +2 -1
- package/dist/__tests__/credentials.test.d.ts +0 -5
- package/dist/__tests__/credentials.test.d.ts.map +0 -1
- package/dist/__tests__/credentials.test.js +0 -169
- package/dist/__tests__/credentials.test.js.map +0 -1
- package/src/__tests__/cloud-setup.test.ts +0 -117
- package/src/__tests__/credentials.test.ts +0 -203
- package/src/__tests__/initial-upload.test.ts +0 -414
- package/src/__tests__/sync.test.ts +0 -627
- package/src/commands/add.ts +0 -74
- package/src/commands/auth.ts +0 -303
- package/src/commands/cloud-setup.ts +0 -251
- package/src/commands/cloud.ts +0 -300
- package/src/commands/initial-upload.ts +0 -263
- package/src/commands/list.ts +0 -66
- package/src/commands/sync.ts +0 -149
- package/src/commands/update.ts +0 -71
- package/src/hq-cloud.d.ts +0 -19
- package/src/index.ts +0 -46
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/sync-worker.ts +0 -82
- package/src/types.ts +0 -47
- package/src/utils/api-client.ts +0 -111
- package/src/utils/credentials.ts +0 -124
- package/src/utils/git.ts +0 -74
- package/src/utils/manifest.ts +0 -111
- package/src/utils/sync.ts +0 -381
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -8
package/dist/index.js
CHANGED
package/dist/utils/api-client.js
CHANGED
|
@@ -12,7 +12,7 @@ import * as path from 'path';
|
|
|
12
12
|
import * as os from 'os';
|
|
13
13
|
import { readCredentials } from './credentials.js';
|
|
14
14
|
/** Default API base URL (production) */
|
|
15
|
-
const DEFAULT_API_URL = 'https://api.hq.
|
|
15
|
+
const DEFAULT_API_URL = 'https://api.hq.getindigo.ai';
|
|
16
16
|
/** Path to optional config file */
|
|
17
17
|
const CONFIG_PATH = path.join(os.homedir(), '.hq', 'config.json');
|
|
18
18
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "HQ management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"url": "https://github.com/indigoai-us/hq.git",
|
|
31
31
|
"directory": "packages/hq-cli"
|
|
32
32
|
},
|
|
33
|
+
"files": ["dist"],
|
|
33
34
|
"keywords": ["hq", "ai", "modules", "sync", "cloud"],
|
|
34
35
|
"license": "MIT",
|
|
35
36
|
"type": "module"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/credentials.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for credential storage (utils/credentials.ts)
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
|
-
import { readCredentials, writeCredentials, clearCredentials, getCredentialsPath, isExpired, _setConfigHome, } from '../utils/credentials.js';
|
|
9
|
-
let tmpDir;
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hq-cli-test-'));
|
|
12
|
-
_setConfigHome(tmpDir);
|
|
13
|
-
});
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
_setConfigHome(null);
|
|
16
|
-
try {
|
|
17
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
// Ignore cleanup errors
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
describe('getCredentialsPath', () => {
|
|
24
|
-
it('returns a path under .hq/ in the config home', () => {
|
|
25
|
-
const p = getCredentialsPath();
|
|
26
|
-
expect(p).toContain('.hq');
|
|
27
|
-
expect(p).toContain('credentials.json');
|
|
28
|
-
expect(p.startsWith(tmpDir)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe('readCredentials', () => {
|
|
32
|
-
it('returns null when no credentials file exists', () => {
|
|
33
|
-
const creds = readCredentials();
|
|
34
|
-
expect(creds).toBeNull();
|
|
35
|
-
});
|
|
36
|
-
it('returns null when credentials file is empty', () => {
|
|
37
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
38
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
39
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), '');
|
|
40
|
-
const creds = readCredentials();
|
|
41
|
-
expect(creds).toBeNull();
|
|
42
|
-
});
|
|
43
|
-
it('returns null when credentials file contains invalid JSON', () => {
|
|
44
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
45
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
46
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), 'not json');
|
|
47
|
-
const creds = readCredentials();
|
|
48
|
-
expect(creds).toBeNull();
|
|
49
|
-
});
|
|
50
|
-
it('returns null when credentials are missing required fields', () => {
|
|
51
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
52
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
53
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), JSON.stringify({ token: 'abc' }) // missing userId
|
|
54
|
-
);
|
|
55
|
-
const creds = readCredentials();
|
|
56
|
-
expect(creds).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
it('returns valid credentials when file is well-formed', () => {
|
|
59
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
60
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
61
|
-
const stored = {
|
|
62
|
-
token: 'test-jwt-token',
|
|
63
|
-
userId: 'user_123',
|
|
64
|
-
email: 'test@example.com',
|
|
65
|
-
storedAt: new Date().toISOString(),
|
|
66
|
-
};
|
|
67
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), JSON.stringify(stored));
|
|
68
|
-
const creds = readCredentials();
|
|
69
|
-
expect(creds).not.toBeNull();
|
|
70
|
-
expect(creds.token).toBe('test-jwt-token');
|
|
71
|
-
expect(creds.userId).toBe('user_123');
|
|
72
|
-
expect(creds.email).toBe('test@example.com');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe('writeCredentials', () => {
|
|
76
|
-
it('creates .hq directory and writes credentials file', () => {
|
|
77
|
-
const creds = {
|
|
78
|
-
token: 'test-token',
|
|
79
|
-
userId: 'user_456',
|
|
80
|
-
email: 'user@test.com',
|
|
81
|
-
storedAt: new Date().toISOString(),
|
|
82
|
-
};
|
|
83
|
-
writeCredentials(creds);
|
|
84
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
85
|
-
expect(fs.existsSync(hqDir)).toBe(true);
|
|
86
|
-
expect(fs.existsSync(path.join(hqDir, 'credentials.json'))).toBe(true);
|
|
87
|
-
const raw = fs.readFileSync(path.join(hqDir, 'credentials.json'), 'utf-8');
|
|
88
|
-
const parsed = JSON.parse(raw);
|
|
89
|
-
expect(parsed.token).toBe('test-token');
|
|
90
|
-
expect(parsed.userId).toBe('user_456');
|
|
91
|
-
});
|
|
92
|
-
it('overwrites existing credentials', () => {
|
|
93
|
-
const creds1 = {
|
|
94
|
-
token: 'token-1',
|
|
95
|
-
userId: 'user_1',
|
|
96
|
-
storedAt: new Date().toISOString(),
|
|
97
|
-
};
|
|
98
|
-
writeCredentials(creds1);
|
|
99
|
-
const creds2 = {
|
|
100
|
-
token: 'token-2',
|
|
101
|
-
userId: 'user_2',
|
|
102
|
-
storedAt: new Date().toISOString(),
|
|
103
|
-
};
|
|
104
|
-
writeCredentials(creds2);
|
|
105
|
-
const read = readCredentials();
|
|
106
|
-
expect(read.token).toBe('token-2');
|
|
107
|
-
expect(read.userId).toBe('user_2');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe('clearCredentials', () => {
|
|
111
|
-
it('returns false when no credentials exist', () => {
|
|
112
|
-
const result = clearCredentials();
|
|
113
|
-
expect(result).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
it('removes credentials file and returns true', () => {
|
|
116
|
-
writeCredentials({
|
|
117
|
-
token: 'test',
|
|
118
|
-
userId: 'user',
|
|
119
|
-
storedAt: new Date().toISOString(),
|
|
120
|
-
});
|
|
121
|
-
expect(readCredentials()).not.toBeNull();
|
|
122
|
-
const result = clearCredentials();
|
|
123
|
-
expect(result).toBe(true);
|
|
124
|
-
expect(readCredentials()).toBeNull();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
describe('isExpired', () => {
|
|
128
|
-
it('returns false when no expiresAt is set', () => {
|
|
129
|
-
const creds = {
|
|
130
|
-
token: 'test',
|
|
131
|
-
userId: 'user',
|
|
132
|
-
storedAt: new Date().toISOString(),
|
|
133
|
-
};
|
|
134
|
-
expect(isExpired(creds)).toBe(false);
|
|
135
|
-
});
|
|
136
|
-
it('returns true when expiresAt is in the past', () => {
|
|
137
|
-
const creds = {
|
|
138
|
-
token: 'test',
|
|
139
|
-
userId: 'user',
|
|
140
|
-
storedAt: new Date().toISOString(),
|
|
141
|
-
expiresAt: new Date(Date.now() - 60_000).toISOString(),
|
|
142
|
-
};
|
|
143
|
-
expect(isExpired(creds)).toBe(true);
|
|
144
|
-
});
|
|
145
|
-
it('returns false when expiresAt is in the future', () => {
|
|
146
|
-
const creds = {
|
|
147
|
-
token: 'test',
|
|
148
|
-
userId: 'user',
|
|
149
|
-
storedAt: new Date().toISOString(),
|
|
150
|
-
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
|
|
151
|
-
};
|
|
152
|
-
expect(isExpired(creds)).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
describe('roundtrip', () => {
|
|
156
|
-
it('write then read returns identical data', () => {
|
|
157
|
-
const original = {
|
|
158
|
-
token: 'jwt-abc-123',
|
|
159
|
-
userId: 'user_roundtrip',
|
|
160
|
-
email: 'roundtrip@test.com',
|
|
161
|
-
storedAt: '2026-01-15T10:00:00.000Z',
|
|
162
|
-
expiresAt: '2026-01-15T11:00:00.000Z',
|
|
163
|
-
};
|
|
164
|
-
writeCredentials(original);
|
|
165
|
-
const read = readCredentials();
|
|
166
|
-
expect(read).toEqual(original);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
//# sourceMappingURL=credentials.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.test.js","sourceRoot":"","sources":["../../src/__tests__/credentials.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for cloud-setup commands (commands/cloud-setup.ts)
|
|
3
|
-
*
|
|
4
|
-
* Tests cover:
|
|
5
|
-
* - Token validation logic (validateClaudeToken)
|
|
6
|
-
* - Command registration (structure)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from 'vitest';
|
|
10
|
-
import { validateClaudeToken } from '../commands/cloud-setup.js';
|
|
11
|
-
import { Command } from 'commander';
|
|
12
|
-
import { registerCloudSetupCommand } from '../commands/cloud-setup.js';
|
|
13
|
-
|
|
14
|
-
describe('validateClaudeToken', () => {
|
|
15
|
-
it('returns error for empty string', () => {
|
|
16
|
-
const result = validateClaudeToken('');
|
|
17
|
-
expect(result).not.toBeNull();
|
|
18
|
-
expect(result).toContain('empty');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('returns error for whitespace-only string', () => {
|
|
22
|
-
const result = validateClaudeToken(' ');
|
|
23
|
-
expect(result).not.toBeNull();
|
|
24
|
-
expect(result).toContain('empty');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('returns error for token shorter than minimum length', () => {
|
|
28
|
-
const result = validateClaudeToken('short');
|
|
29
|
-
expect(result).not.toBeNull();
|
|
30
|
-
expect(result).toContain('too short');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('returns error for token with exactly 19 chars (below minimum)', () => {
|
|
34
|
-
const result = validateClaudeToken('a'.repeat(19));
|
|
35
|
-
expect(result).not.toBeNull();
|
|
36
|
-
expect(result).toContain('too short');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('returns null for token with exactly 20 chars (at minimum)', () => {
|
|
40
|
-
const result = validateClaudeToken('a'.repeat(20));
|
|
41
|
-
expect(result).toBeNull();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('returns null for a long valid token', () => {
|
|
45
|
-
const token = 'sk-ant-' + 'a'.repeat(100);
|
|
46
|
-
const result = validateClaudeToken(token);
|
|
47
|
-
expect(result).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('returns error for token containing whitespace in the middle', () => {
|
|
51
|
-
const result = validateClaudeToken('abc def ghi jkl mno pqr');
|
|
52
|
-
expect(result).not.toBeNull();
|
|
53
|
-
expect(result).toContain('whitespace');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns error for token with newlines', () => {
|
|
57
|
-
const result = validateClaudeToken('abcdefghijklmnopqrst\nuvwxyz');
|
|
58
|
-
expect(result).not.toBeNull();
|
|
59
|
-
expect(result).toContain('whitespace');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('returns error for token with tabs', () => {
|
|
63
|
-
const result = validateClaudeToken('abcdefghijklmnopqrst\tuvwxyz');
|
|
64
|
-
expect(result).not.toBeNull();
|
|
65
|
-
expect(result).toContain('whitespace');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('trims leading and trailing whitespace before validation', () => {
|
|
69
|
-
// 20+ chars after trimming, no internal whitespace
|
|
70
|
-
const result = validateClaudeToken(' ' + 'a'.repeat(25) + ' ');
|
|
71
|
-
expect(result).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('returns null for a realistic OAuth token', () => {
|
|
75
|
-
// Simulating a realistic token format
|
|
76
|
-
const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature';
|
|
77
|
-
const result = validateClaudeToken(token);
|
|
78
|
-
expect(result).toBeNull();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('registerCloudSetupCommand', () => {
|
|
83
|
-
it('registers "cloud" command with "setup-token" and "status" subcommands', () => {
|
|
84
|
-
const program = new Command();
|
|
85
|
-
registerCloudSetupCommand(program);
|
|
86
|
-
|
|
87
|
-
// Find the cloud command
|
|
88
|
-
const cloudCmd = program.commands.find((c) => c.name() === 'cloud');
|
|
89
|
-
expect(cloudCmd).toBeDefined();
|
|
90
|
-
expect(cloudCmd!.description()).toBe('Cloud session management — token setup and status');
|
|
91
|
-
|
|
92
|
-
// Check subcommands
|
|
93
|
-
const subcommandNames = cloudCmd!.commands.map((c) => c.name());
|
|
94
|
-
expect(subcommandNames).toContain('setup-token');
|
|
95
|
-
expect(subcommandNames).toContain('status');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('"setup-token" subcommand has correct description', () => {
|
|
99
|
-
const program = new Command();
|
|
100
|
-
registerCloudSetupCommand(program);
|
|
101
|
-
|
|
102
|
-
const cloudCmd = program.commands.find((c) => c.name() === 'cloud');
|
|
103
|
-
const setupTokenCmd = cloudCmd!.commands.find((c) => c.name() === 'setup-token');
|
|
104
|
-
expect(setupTokenCmd).toBeDefined();
|
|
105
|
-
expect(setupTokenCmd!.description()).toContain('Claude OAuth token');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('"status" subcommand has correct description', () => {
|
|
109
|
-
const program = new Command();
|
|
110
|
-
registerCloudSetupCommand(program);
|
|
111
|
-
|
|
112
|
-
const cloudCmd = program.commands.find((c) => c.name() === 'cloud');
|
|
113
|
-
const statusCmd = cloudCmd!.commands.find((c) => c.name() === 'status');
|
|
114
|
-
expect(statusCmd).toBeDefined();
|
|
115
|
-
expect(statusCmd!.description()).toContain('status');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for credential storage (utils/credentials.ts)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
import * as os from 'os';
|
|
9
|
-
import {
|
|
10
|
-
readCredentials,
|
|
11
|
-
writeCredentials,
|
|
12
|
-
clearCredentials,
|
|
13
|
-
getCredentialsPath,
|
|
14
|
-
isExpired,
|
|
15
|
-
_setConfigHome,
|
|
16
|
-
} from '../utils/credentials.js';
|
|
17
|
-
import type { HqCredentials } from '../utils/credentials.js';
|
|
18
|
-
|
|
19
|
-
let tmpDir: string;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hq-cli-test-'));
|
|
23
|
-
_setConfigHome(tmpDir);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
_setConfigHome(null);
|
|
28
|
-
try {
|
|
29
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
30
|
-
} catch {
|
|
31
|
-
// Ignore cleanup errors
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('getCredentialsPath', () => {
|
|
36
|
-
it('returns a path under .hq/ in the config home', () => {
|
|
37
|
-
const p = getCredentialsPath();
|
|
38
|
-
expect(p).toContain('.hq');
|
|
39
|
-
expect(p).toContain('credentials.json');
|
|
40
|
-
expect(p.startsWith(tmpDir)).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('readCredentials', () => {
|
|
45
|
-
it('returns null when no credentials file exists', () => {
|
|
46
|
-
const creds = readCredentials();
|
|
47
|
-
expect(creds).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('returns null when credentials file is empty', () => {
|
|
51
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
52
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
53
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), '');
|
|
54
|
-
const creds = readCredentials();
|
|
55
|
-
expect(creds).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('returns null when credentials file contains invalid JSON', () => {
|
|
59
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
60
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
61
|
-
fs.writeFileSync(path.join(hqDir, 'credentials.json'), 'not json');
|
|
62
|
-
const creds = readCredentials();
|
|
63
|
-
expect(creds).toBeNull();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns null when credentials are missing required fields', () => {
|
|
67
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
68
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
69
|
-
fs.writeFileSync(
|
|
70
|
-
path.join(hqDir, 'credentials.json'),
|
|
71
|
-
JSON.stringify({ token: 'abc' }) // missing userId
|
|
72
|
-
);
|
|
73
|
-
const creds = readCredentials();
|
|
74
|
-
expect(creds).toBeNull();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('returns valid credentials when file is well-formed', () => {
|
|
78
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
79
|
-
fs.mkdirSync(hqDir, { recursive: true });
|
|
80
|
-
const stored: HqCredentials = {
|
|
81
|
-
token: 'test-jwt-token',
|
|
82
|
-
userId: 'user_123',
|
|
83
|
-
email: 'test@example.com',
|
|
84
|
-
storedAt: new Date().toISOString(),
|
|
85
|
-
};
|
|
86
|
-
fs.writeFileSync(
|
|
87
|
-
path.join(hqDir, 'credentials.json'),
|
|
88
|
-
JSON.stringify(stored)
|
|
89
|
-
);
|
|
90
|
-
const creds = readCredentials();
|
|
91
|
-
expect(creds).not.toBeNull();
|
|
92
|
-
expect(creds!.token).toBe('test-jwt-token');
|
|
93
|
-
expect(creds!.userId).toBe('user_123');
|
|
94
|
-
expect(creds!.email).toBe('test@example.com');
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('writeCredentials', () => {
|
|
99
|
-
it('creates .hq directory and writes credentials file', () => {
|
|
100
|
-
const creds: HqCredentials = {
|
|
101
|
-
token: 'test-token',
|
|
102
|
-
userId: 'user_456',
|
|
103
|
-
email: 'user@test.com',
|
|
104
|
-
storedAt: new Date().toISOString(),
|
|
105
|
-
};
|
|
106
|
-
writeCredentials(creds);
|
|
107
|
-
|
|
108
|
-
const hqDir = path.join(tmpDir, '.hq');
|
|
109
|
-
expect(fs.existsSync(hqDir)).toBe(true);
|
|
110
|
-
expect(fs.existsSync(path.join(hqDir, 'credentials.json'))).toBe(true);
|
|
111
|
-
|
|
112
|
-
const raw = fs.readFileSync(path.join(hqDir, 'credentials.json'), 'utf-8');
|
|
113
|
-
const parsed = JSON.parse(raw);
|
|
114
|
-
expect(parsed.token).toBe('test-token');
|
|
115
|
-
expect(parsed.userId).toBe('user_456');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('overwrites existing credentials', () => {
|
|
119
|
-
const creds1: HqCredentials = {
|
|
120
|
-
token: 'token-1',
|
|
121
|
-
userId: 'user_1',
|
|
122
|
-
storedAt: new Date().toISOString(),
|
|
123
|
-
};
|
|
124
|
-
writeCredentials(creds1);
|
|
125
|
-
|
|
126
|
-
const creds2: HqCredentials = {
|
|
127
|
-
token: 'token-2',
|
|
128
|
-
userId: 'user_2',
|
|
129
|
-
storedAt: new Date().toISOString(),
|
|
130
|
-
};
|
|
131
|
-
writeCredentials(creds2);
|
|
132
|
-
|
|
133
|
-
const read = readCredentials();
|
|
134
|
-
expect(read!.token).toBe('token-2');
|
|
135
|
-
expect(read!.userId).toBe('user_2');
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe('clearCredentials', () => {
|
|
140
|
-
it('returns false when no credentials exist', () => {
|
|
141
|
-
const result = clearCredentials();
|
|
142
|
-
expect(result).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('removes credentials file and returns true', () => {
|
|
146
|
-
writeCredentials({
|
|
147
|
-
token: 'test',
|
|
148
|
-
userId: 'user',
|
|
149
|
-
storedAt: new Date().toISOString(),
|
|
150
|
-
});
|
|
151
|
-
expect(readCredentials()).not.toBeNull();
|
|
152
|
-
|
|
153
|
-
const result = clearCredentials();
|
|
154
|
-
expect(result).toBe(true);
|
|
155
|
-
expect(readCredentials()).toBeNull();
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('isExpired', () => {
|
|
160
|
-
it('returns false when no expiresAt is set', () => {
|
|
161
|
-
const creds: HqCredentials = {
|
|
162
|
-
token: 'test',
|
|
163
|
-
userId: 'user',
|
|
164
|
-
storedAt: new Date().toISOString(),
|
|
165
|
-
};
|
|
166
|
-
expect(isExpired(creds)).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('returns true when expiresAt is in the past', () => {
|
|
170
|
-
const creds: HqCredentials = {
|
|
171
|
-
token: 'test',
|
|
172
|
-
userId: 'user',
|
|
173
|
-
storedAt: new Date().toISOString(),
|
|
174
|
-
expiresAt: new Date(Date.now() - 60_000).toISOString(),
|
|
175
|
-
};
|
|
176
|
-
expect(isExpired(creds)).toBe(true);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('returns false when expiresAt is in the future', () => {
|
|
180
|
-
const creds: HqCredentials = {
|
|
181
|
-
token: 'test',
|
|
182
|
-
userId: 'user',
|
|
183
|
-
storedAt: new Date().toISOString(),
|
|
184
|
-
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
|
|
185
|
-
};
|
|
186
|
-
expect(isExpired(creds)).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('roundtrip', () => {
|
|
191
|
-
it('write then read returns identical data', () => {
|
|
192
|
-
const original: HqCredentials = {
|
|
193
|
-
token: 'jwt-abc-123',
|
|
194
|
-
userId: 'user_roundtrip',
|
|
195
|
-
email: 'roundtrip@test.com',
|
|
196
|
-
storedAt: '2026-01-15T10:00:00.000Z',
|
|
197
|
-
expiresAt: '2026-01-15T11:00:00.000Z',
|
|
198
|
-
};
|
|
199
|
-
writeCredentials(original);
|
|
200
|
-
const read = readCredentials();
|
|
201
|
-
expect(read).toEqual(original);
|
|
202
|
-
});
|
|
203
|
-
});
|