@cpretzinger/boss-claude 1.0.0 → 1.0.2
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/README.md +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Boss Claude - Integration Test
|
|
4
|
+
*
|
|
5
|
+
* Validates entire system end-to-end:
|
|
6
|
+
* 1. Redis connectivity and operations
|
|
7
|
+
* 2. PostgreSQL connectivity and queries
|
|
8
|
+
* 3. GitHub API integration
|
|
9
|
+
* 4. Environment configuration
|
|
10
|
+
*
|
|
11
|
+
* Usage: node lib/setup/integration-test.js
|
|
12
|
+
* Or: boss-claude test
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import ora from 'ora';
|
|
17
|
+
import Redis from 'ioredis';
|
|
18
|
+
import dotenv from 'dotenv';
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import { Octokit } from '@octokit/rest';
|
|
23
|
+
import postgres from '../postgres.js';
|
|
24
|
+
|
|
25
|
+
// Load environment
|
|
26
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
27
|
+
if (existsSync(envPath)) {
|
|
28
|
+
dotenv.config({ path: envPath });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Test results tracker
|
|
32
|
+
const results = {
|
|
33
|
+
passed: 0,
|
|
34
|
+
failed: 0,
|
|
35
|
+
warnings: 0,
|
|
36
|
+
tests: []
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Helper functions
|
|
40
|
+
function success(message) {
|
|
41
|
+
console.log(chalk.green('✓'), message);
|
|
42
|
+
results.passed++;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fail(message, error) {
|
|
46
|
+
console.log(chalk.red('✗'), message);
|
|
47
|
+
if (error) {
|
|
48
|
+
console.log(chalk.red(' Error:'), error.message);
|
|
49
|
+
}
|
|
50
|
+
results.failed++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function warning(message) {
|
|
54
|
+
console.log(chalk.yellow('⚠'), message);
|
|
55
|
+
results.warnings++;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function info(message) {
|
|
59
|
+
console.log(chalk.blue('ℹ'), message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function section(title) {
|
|
63
|
+
console.log('\n' + chalk.bold.cyan(`━━━ ${title} ━━━`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function subsection(title) {
|
|
67
|
+
console.log(chalk.bold.white(`\n${title}:`));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Test implementations
|
|
71
|
+
async function testEnvironmentVariables() {
|
|
72
|
+
section('Environment Configuration');
|
|
73
|
+
|
|
74
|
+
const requiredVars = [
|
|
75
|
+
{ key: 'REDIS_URL', description: 'Redis connection string' },
|
|
76
|
+
{ key: 'BOSS_CLAUDE_PG_URL', description: 'PostgreSQL connection string' },
|
|
77
|
+
{ key: 'GITHUB_TOKEN', description: 'GitHub API token' }
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const optionalVars = [
|
|
81
|
+
{ key: 'GITHUB_OWNER', description: 'GitHub username' },
|
|
82
|
+
{ key: 'GITHUB_MEMORY_REPO', description: 'Memory repository name' }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
subsection('Required Variables');
|
|
86
|
+
for (const { key, description } of requiredVars) {
|
|
87
|
+
if (process.env[key]) {
|
|
88
|
+
const maskedValue = key.includes('TOKEN') || key.includes('PASSWORD') || key.includes('URL')
|
|
89
|
+
? '***' + process.env[key].slice(-4)
|
|
90
|
+
: process.env[key];
|
|
91
|
+
success(`${key} (${description}): ${maskedValue}`);
|
|
92
|
+
} else {
|
|
93
|
+
fail(`${key} (${description}): NOT SET`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
subsection('Optional Variables');
|
|
98
|
+
for (const { key, description } of optionalVars) {
|
|
99
|
+
if (process.env[key]) {
|
|
100
|
+
success(`${key} (${description}): ${process.env[key]}`);
|
|
101
|
+
} else {
|
|
102
|
+
warning(`${key} (${description}): Not set (using defaults)`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check .env file location
|
|
107
|
+
if (existsSync(envPath)) {
|
|
108
|
+
success(`Config file: ${envPath}`);
|
|
109
|
+
} else {
|
|
110
|
+
fail(`Config file not found: ${envPath}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function testRedisConnection() {
|
|
115
|
+
section('Redis Connection & Operations');
|
|
116
|
+
|
|
117
|
+
let redis = null;
|
|
118
|
+
const testKey = `boss:test:${Date.now()}`;
|
|
119
|
+
const testValue = JSON.stringify({ test: true, timestamp: Date.now() });
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
subsection('Connection Test');
|
|
123
|
+
const spinner = ora('Connecting to Redis...').start();
|
|
124
|
+
|
|
125
|
+
redis = new Redis(process.env.REDIS_URL);
|
|
126
|
+
|
|
127
|
+
// Wait for connection with timeout
|
|
128
|
+
await Promise.race([
|
|
129
|
+
new Promise((resolve) => redis.once('ready', resolve)),
|
|
130
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Connection timeout')), 5000))
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
spinner.succeed('Connected to Redis');
|
|
134
|
+
success('Redis connection established');
|
|
135
|
+
|
|
136
|
+
// Get server info
|
|
137
|
+
const info = await redis.info('server');
|
|
138
|
+
const versionMatch = info.match(/redis_version:([^\r\n]+)/);
|
|
139
|
+
if (versionMatch) {
|
|
140
|
+
success(`Redis version: ${versionMatch[1]}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
subsection('Write Operations');
|
|
144
|
+
|
|
145
|
+
// Test SET
|
|
146
|
+
const setResult = await redis.set(testKey, testValue);
|
|
147
|
+
if (setResult === 'OK') {
|
|
148
|
+
success('SET operation successful');
|
|
149
|
+
} else {
|
|
150
|
+
fail('SET operation failed', new Error(`Unexpected result: ${setResult}`));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Test TTL
|
|
154
|
+
const ttlResult = await redis.expire(testKey, 60);
|
|
155
|
+
if (ttlResult === 1) {
|
|
156
|
+
success('TTL set successfully (60 seconds)');
|
|
157
|
+
} else {
|
|
158
|
+
fail('TTL operation failed');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
subsection('Read Operations');
|
|
162
|
+
|
|
163
|
+
// Test GET
|
|
164
|
+
const getValue = await redis.get(testKey);
|
|
165
|
+
if (getValue === testValue) {
|
|
166
|
+
success('GET operation successful');
|
|
167
|
+
} else {
|
|
168
|
+
fail('GET operation failed', new Error('Value mismatch'));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Test EXISTS
|
|
172
|
+
const existsResult = await redis.exists(testKey);
|
|
173
|
+
if (existsResult === 1) {
|
|
174
|
+
success('EXISTS operation successful');
|
|
175
|
+
} else {
|
|
176
|
+
fail('EXISTS operation failed');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
subsection('Boss Claude Identity Test');
|
|
180
|
+
|
|
181
|
+
// Test Boss identity operations
|
|
182
|
+
const identityKey = 'boss:identity:test';
|
|
183
|
+
const identityData = {
|
|
184
|
+
level: 1,
|
|
185
|
+
xp: 50,
|
|
186
|
+
token_bank: 1000,
|
|
187
|
+
total_sessions: 5,
|
|
188
|
+
repos_managed: 3,
|
|
189
|
+
created_at: new Date().toISOString()
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
await redis.set(identityKey, JSON.stringify(identityData));
|
|
193
|
+
const retrievedIdentity = await redis.get(identityKey);
|
|
194
|
+
const parsedIdentity = JSON.parse(retrievedIdentity);
|
|
195
|
+
|
|
196
|
+
if (parsedIdentity.level === 1 && parsedIdentity.xp === 50) {
|
|
197
|
+
success('Identity data serialization working correctly');
|
|
198
|
+
} else {
|
|
199
|
+
fail('Identity data mismatch');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
subsection('Cleanup');
|
|
203
|
+
|
|
204
|
+
// Clean up test keys
|
|
205
|
+
await redis.del(testKey, identityKey);
|
|
206
|
+
success('Test keys cleaned up');
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
fail('Redis test failed', error);
|
|
210
|
+
} finally {
|
|
211
|
+
if (redis) {
|
|
212
|
+
await redis.quit();
|
|
213
|
+
info('Redis connection closed');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function testPostgreSQLConnection() {
|
|
219
|
+
section('PostgreSQL Connection & Operations');
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
subsection('Connection Test');
|
|
223
|
+
const spinner = ora('Testing PostgreSQL connection...').start();
|
|
224
|
+
|
|
225
|
+
const connectionTest = await postgres.utils.testConnection();
|
|
226
|
+
|
|
227
|
+
if (connectionTest.connected) {
|
|
228
|
+
spinner.succeed('Connected to PostgreSQL');
|
|
229
|
+
success('PostgreSQL connection established');
|
|
230
|
+
|
|
231
|
+
const versionParts = connectionTest.version.split(' ');
|
|
232
|
+
success(`PostgreSQL version: ${versionParts[1]}`);
|
|
233
|
+
success(`Server time: ${connectionTest.timestamp.toISOString()}`);
|
|
234
|
+
} else {
|
|
235
|
+
spinner.fail('PostgreSQL connection failed');
|
|
236
|
+
fail('PostgreSQL connection failed', new Error(connectionTest.error));
|
|
237
|
+
return; // Skip remaining tests
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
subsection('Schema Validation');
|
|
241
|
+
|
|
242
|
+
// Test schema existence
|
|
243
|
+
const schemaQuery = `
|
|
244
|
+
SELECT schema_name
|
|
245
|
+
FROM information_schema.schemata
|
|
246
|
+
WHERE schema_name = 'boss_claude'
|
|
247
|
+
`;
|
|
248
|
+
const schemaResult = await postgres.pool.query(schemaQuery);
|
|
249
|
+
|
|
250
|
+
if (schemaResult.rows.length > 0) {
|
|
251
|
+
success('boss_claude schema exists');
|
|
252
|
+
} else {
|
|
253
|
+
fail('boss_claude schema not found');
|
|
254
|
+
warning('Run database setup: boss-claude setup-db');
|
|
255
|
+
return; // Skip table tests
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
subsection('Table Validation');
|
|
259
|
+
|
|
260
|
+
const tables = ['sessions', 'achievements', 'memory_snapshots'];
|
|
261
|
+
|
|
262
|
+
for (const table of tables) {
|
|
263
|
+
const tableQuery = `
|
|
264
|
+
SELECT table_name
|
|
265
|
+
FROM information_schema.tables
|
|
266
|
+
WHERE table_schema = 'boss_claude'
|
|
267
|
+
AND table_name = $1
|
|
268
|
+
`;
|
|
269
|
+
const tableResult = await postgres.pool.query(tableQuery, [table]);
|
|
270
|
+
|
|
271
|
+
if (tableResult.rows.length > 0) {
|
|
272
|
+
success(`Table boss_claude.${table} exists`);
|
|
273
|
+
} else {
|
|
274
|
+
fail(`Table boss_claude.${table} not found`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
subsection('Function Validation');
|
|
279
|
+
|
|
280
|
+
const functions = [
|
|
281
|
+
'fn_get_current_session',
|
|
282
|
+
'fn_get_user_stats'
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
for (const func of functions) {
|
|
286
|
+
const funcQuery = `
|
|
287
|
+
SELECT routine_name
|
|
288
|
+
FROM information_schema.routines
|
|
289
|
+
WHERE routine_schema = 'boss_claude'
|
|
290
|
+
AND routine_name = $1
|
|
291
|
+
`;
|
|
292
|
+
const funcResult = await postgres.pool.query(funcQuery, [func]);
|
|
293
|
+
|
|
294
|
+
if (funcResult.rows.length > 0) {
|
|
295
|
+
success(`Function boss_claude.${func} exists`);
|
|
296
|
+
} else {
|
|
297
|
+
warning(`Function boss_claude.${func} not found`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
subsection('Test Session Operations');
|
|
302
|
+
|
|
303
|
+
const testUserId = `test-user-${Date.now()}`;
|
|
304
|
+
const testProject = 'integration-test';
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Create test session
|
|
308
|
+
const session = await postgres.sessions.start(
|
|
309
|
+
testUserId,
|
|
310
|
+
testProject,
|
|
311
|
+
1,
|
|
312
|
+
{ test: true, timestamp: Date.now() }
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (session && session.id) {
|
|
316
|
+
success(`Created test session: ${session.id}`);
|
|
317
|
+
|
|
318
|
+
// Update session
|
|
319
|
+
const updated = await postgres.sessions.updateProgress(session.id, {
|
|
320
|
+
xpEarned: 100,
|
|
321
|
+
tokensSaved: 500,
|
|
322
|
+
tasksCompleted: 5,
|
|
323
|
+
perfectExecutions: 2,
|
|
324
|
+
efficiency: 1.5
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (updated.xp_earned === 100 && updated.tokens_saved === 500) {
|
|
328
|
+
success('Session progress update successful');
|
|
329
|
+
} else {
|
|
330
|
+
fail('Session progress update failed');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// End session
|
|
334
|
+
const ended = await postgres.sessions.end(
|
|
335
|
+
session.id,
|
|
336
|
+
2,
|
|
337
|
+
'Integration test completed successfully'
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (ended.end_time) {
|
|
341
|
+
success('Session ended successfully');
|
|
342
|
+
} else {
|
|
343
|
+
fail('Session end failed');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Cleanup: Delete test session
|
|
347
|
+
await postgres.pool.query(
|
|
348
|
+
'DELETE FROM boss_claude.sessions WHERE user_id = $1',
|
|
349
|
+
[testUserId]
|
|
350
|
+
);
|
|
351
|
+
success('Test session cleaned up');
|
|
352
|
+
|
|
353
|
+
} else {
|
|
354
|
+
fail('Failed to create test session');
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
fail('Session operations failed', error);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
} catch (error) {
|
|
361
|
+
fail('PostgreSQL test failed', error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function testGitHubIntegration() {
|
|
366
|
+
section('GitHub API Integration');
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
370
|
+
fail('GITHUB_TOKEN not set - skipping GitHub tests');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
subsection('Authentication Test');
|
|
375
|
+
const spinner = ora('Testing GitHub authentication...').start();
|
|
376
|
+
|
|
377
|
+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
378
|
+
|
|
379
|
+
// Test authentication by fetching user info
|
|
380
|
+
const { data: user } = await octokit.users.getAuthenticated();
|
|
381
|
+
|
|
382
|
+
spinner.succeed('GitHub authentication successful');
|
|
383
|
+
success(`Authenticated as: ${user.login}`);
|
|
384
|
+
success(`Account type: ${user.type}`);
|
|
385
|
+
|
|
386
|
+
subsection('Repository Access');
|
|
387
|
+
|
|
388
|
+
const owner = process.env.GITHUB_OWNER || user.login;
|
|
389
|
+
const repo = process.env.GITHUB_MEMORY_REPO || 'boss-claude-memory';
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
// Check if memory repo exists
|
|
393
|
+
const { data: repoData } = await octokit.repos.get({ owner, repo });
|
|
394
|
+
|
|
395
|
+
success(`Memory repository accessible: ${owner}/${repo}`);
|
|
396
|
+
success(`Repository visibility: ${repoData.private ? 'Private' : 'Public'}`);
|
|
397
|
+
|
|
398
|
+
// Check permissions
|
|
399
|
+
const permissions = repoData.permissions || {};
|
|
400
|
+
const hasIssues = repoData.has_issues;
|
|
401
|
+
|
|
402
|
+
if (permissions.push || permissions.admin) {
|
|
403
|
+
success('Write permissions: YES');
|
|
404
|
+
} else {
|
|
405
|
+
warning('Write permissions: NO - may not be able to create issues');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (hasIssues) {
|
|
409
|
+
success('Issues enabled: YES');
|
|
410
|
+
} else {
|
|
411
|
+
fail('Issues not enabled - required for memory storage');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
subsection('Issue Operations Test');
|
|
415
|
+
|
|
416
|
+
// Create a test issue
|
|
417
|
+
const testIssue = await octokit.issues.create({
|
|
418
|
+
owner,
|
|
419
|
+
repo,
|
|
420
|
+
title: `[integration-test] Test Issue ${Date.now()}`,
|
|
421
|
+
body: '## Test Issue\n\nThis is an automated test issue created by Boss Claude integration tests.\n\n**Status:** Will be automatically closed.',
|
|
422
|
+
labels: ['test', 'automated']
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
if (testIssue.data.number) {
|
|
426
|
+
success(`Created test issue #${testIssue.data.number}`);
|
|
427
|
+
|
|
428
|
+
// Close the test issue
|
|
429
|
+
await octokit.issues.update({
|
|
430
|
+
owner,
|
|
431
|
+
repo,
|
|
432
|
+
issue_number: testIssue.data.number,
|
|
433
|
+
state: 'closed'
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
success(`Closed test issue #${testIssue.data.number}`);
|
|
437
|
+
} else {
|
|
438
|
+
fail('Failed to create test issue');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
} catch (error) {
|
|
442
|
+
if (error.status === 404) {
|
|
443
|
+
warning(`Memory repository not found: ${owner}/${repo}`);
|
|
444
|
+
info(`Create it with: boss-claude setup`);
|
|
445
|
+
} else {
|
|
446
|
+
fail('Repository access test failed', error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
subsection('API Rate Limits');
|
|
451
|
+
|
|
452
|
+
const { data: rateLimit } = await octokit.rateLimit.get();
|
|
453
|
+
const { core } = rateLimit.resources;
|
|
454
|
+
|
|
455
|
+
const remaining = core.remaining;
|
|
456
|
+
const limit = core.limit;
|
|
457
|
+
const resetTime = new Date(core.reset * 1000);
|
|
458
|
+
|
|
459
|
+
success(`Rate limit: ${remaining}/${limit} requests remaining`);
|
|
460
|
+
info(`Resets at: ${resetTime.toLocaleTimeString()}`);
|
|
461
|
+
|
|
462
|
+
if (remaining < 100) {
|
|
463
|
+
warning('Low API rate limit remaining');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
} catch (error) {
|
|
467
|
+
fail('GitHub integration test failed', error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function testSystemIntegration() {
|
|
472
|
+
section('Full System Integration');
|
|
473
|
+
|
|
474
|
+
subsection('End-to-End Workflow Test');
|
|
475
|
+
|
|
476
|
+
const testData = {
|
|
477
|
+
userId: `test-user-${Date.now()}`,
|
|
478
|
+
project: 'integration-test-e2e',
|
|
479
|
+
repo: 'test-repo'
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
let redis = null;
|
|
483
|
+
let sessionId = null;
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
// Step 1: Redis - Store identity
|
|
487
|
+
info('Step 1: Creating identity in Redis...');
|
|
488
|
+
redis = new Redis(process.env.REDIS_URL);
|
|
489
|
+
|
|
490
|
+
const identityKey = `boss:identity:${testData.userId}`;
|
|
491
|
+
const identity = {
|
|
492
|
+
level: 1,
|
|
493
|
+
xp: 0,
|
|
494
|
+
token_bank: 0,
|
|
495
|
+
total_sessions: 0,
|
|
496
|
+
repos_managed: 1,
|
|
497
|
+
created_at: new Date().toISOString()
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
await redis.set(identityKey, JSON.stringify(identity));
|
|
501
|
+
success('Identity stored in Redis');
|
|
502
|
+
|
|
503
|
+
// Step 2: PostgreSQL - Create session
|
|
504
|
+
info('Step 2: Creating session in PostgreSQL...');
|
|
505
|
+
const session = await postgres.sessions.start(
|
|
506
|
+
testData.userId,
|
|
507
|
+
testData.project,
|
|
508
|
+
identity.level,
|
|
509
|
+
{ test: true, workflow: 'e2e' }
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
sessionId = session.id;
|
|
513
|
+
success(`Session created: ${sessionId}`);
|
|
514
|
+
|
|
515
|
+
// Step 3: Update session with progress
|
|
516
|
+
info('Step 3: Recording session progress...');
|
|
517
|
+
await postgres.sessions.updateProgress(sessionId, {
|
|
518
|
+
xpEarned: 150,
|
|
519
|
+
tokensSaved: 1000,
|
|
520
|
+
tasksCompleted: 3,
|
|
521
|
+
perfectExecutions: 2
|
|
522
|
+
});
|
|
523
|
+
success('Session progress recorded');
|
|
524
|
+
|
|
525
|
+
// Step 4: Update identity in Redis
|
|
526
|
+
info('Step 4: Updating identity in Redis...');
|
|
527
|
+
identity.xp += 150;
|
|
528
|
+
identity.token_bank += 1000;
|
|
529
|
+
identity.total_sessions += 1;
|
|
530
|
+
|
|
531
|
+
await redis.set(identityKey, JSON.stringify(identity));
|
|
532
|
+
success('Identity updated in Redis');
|
|
533
|
+
|
|
534
|
+
// Step 5: End session
|
|
535
|
+
info('Step 5: Ending session...');
|
|
536
|
+
await postgres.sessions.end(
|
|
537
|
+
sessionId,
|
|
538
|
+
identity.level,
|
|
539
|
+
'Integration test completed - all systems operational'
|
|
540
|
+
);
|
|
541
|
+
success('Session ended successfully');
|
|
542
|
+
|
|
543
|
+
// Step 6: Verify data consistency
|
|
544
|
+
info('Step 6: Verifying data consistency...');
|
|
545
|
+
|
|
546
|
+
const storedIdentity = JSON.parse(await redis.get(identityKey));
|
|
547
|
+
const sessionRecord = await postgres.pool.query(
|
|
548
|
+
'SELECT * FROM boss_claude.sessions WHERE id = $1',
|
|
549
|
+
[sessionId]
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
if (storedIdentity.xp === 150 && sessionRecord.rows[0].xp_earned === 150) {
|
|
553
|
+
success('Data consistency verified across Redis and PostgreSQL');
|
|
554
|
+
} else {
|
|
555
|
+
fail('Data inconsistency detected');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Cleanup
|
|
559
|
+
info('Cleaning up test data...');
|
|
560
|
+
await redis.del(identityKey);
|
|
561
|
+
await postgres.pool.query(
|
|
562
|
+
'DELETE FROM boss_claude.sessions WHERE user_id = $1',
|
|
563
|
+
[testData.userId]
|
|
564
|
+
);
|
|
565
|
+
success('Test data cleaned up');
|
|
566
|
+
|
|
567
|
+
subsection('Integration Test Summary');
|
|
568
|
+
success('Full end-to-end workflow completed successfully');
|
|
569
|
+
success('All systems are properly integrated and operational');
|
|
570
|
+
|
|
571
|
+
} catch (error) {
|
|
572
|
+
fail('System integration test failed', error);
|
|
573
|
+
} finally {
|
|
574
|
+
if (redis) {
|
|
575
|
+
await redis.quit();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function printSummary() {
|
|
581
|
+
console.log('\n' + chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
582
|
+
console.log(chalk.bold.white('INTEGRATION TEST SUMMARY'));
|
|
583
|
+
console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
584
|
+
|
|
585
|
+
console.log(chalk.green('Passed: '), results.passed);
|
|
586
|
+
console.log(chalk.red('Failed: '), results.failed);
|
|
587
|
+
console.log(chalk.yellow('Warnings:'), results.warnings);
|
|
588
|
+
console.log(chalk.white('Total: '), results.passed + results.failed);
|
|
589
|
+
|
|
590
|
+
const passRate = Math.round((results.passed / (results.passed + results.failed)) * 100);
|
|
591
|
+
|
|
592
|
+
console.log('\n' + chalk.bold.white('Pass Rate:'),
|
|
593
|
+
passRate >= 90 ? chalk.green(`${passRate}%`) :
|
|
594
|
+
passRate >= 70 ? chalk.yellow(`${passRate}%`) :
|
|
595
|
+
chalk.red(`${passRate}%`)
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
if (results.failed === 0) {
|
|
599
|
+
console.log('\n' + chalk.bold.green('✓ ALL TESTS PASSED - System is fully operational!'));
|
|
600
|
+
} else if (results.failed < 5) {
|
|
601
|
+
console.log('\n' + chalk.bold.yellow('⚠ Some tests failed - System partially operational'));
|
|
602
|
+
} else {
|
|
603
|
+
console.log('\n' + chalk.bold.red('✗ Multiple failures detected - System needs attention'));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
console.log('\n' + chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
607
|
+
|
|
608
|
+
// Exit with appropriate code
|
|
609
|
+
process.exit(results.failed > 0 ? 1 : 0);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Main execution
|
|
613
|
+
async function runIntegrationTests() {
|
|
614
|
+
console.log(chalk.bold.cyan('\n╔══════════════════════════════════════════════╗'));
|
|
615
|
+
console.log(chalk.bold.cyan('║ BOSS CLAUDE - INTEGRATION TEST SUITE ║'));
|
|
616
|
+
console.log(chalk.bold.cyan('╚══════════════════════════════════════════════╝\n'));
|
|
617
|
+
|
|
618
|
+
info('Testing complete system integration...');
|
|
619
|
+
info(`Environment: ${envPath}\n`);
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
// Run all test suites
|
|
623
|
+
await testEnvironmentVariables();
|
|
624
|
+
await testRedisConnection();
|
|
625
|
+
await testPostgreSQLConnection();
|
|
626
|
+
await testGitHubIntegration();
|
|
627
|
+
await testSystemIntegration();
|
|
628
|
+
|
|
629
|
+
// Print final summary
|
|
630
|
+
printSummary();
|
|
631
|
+
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.error(chalk.red('\n✗ Fatal error during integration tests:'));
|
|
634
|
+
console.error(chalk.red(error.message));
|
|
635
|
+
console.error(error.stack);
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Run tests if executed directly
|
|
641
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
642
|
+
runIntegrationTests();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export {
|
|
646
|
+
runIntegrationTests,
|
|
647
|
+
testEnvironmentVariables,
|
|
648
|
+
testRedisConnection,
|
|
649
|
+
testPostgreSQLConnection,
|
|
650
|
+
testGitHubIntegration,
|
|
651
|
+
testSystemIntegration
|
|
652
|
+
};
|