import { ConvexHttpClient } from 'convex/browser'; import { makeFunctionReference } from 'convex/server'; import { ConvexStore } from '../src/lib/server/store'; import { applicationJwt, hashSecret, randomSecret } from '../src/lib/server/security'; import assert from 'node:assert/strict'; import { SignJWT, importPKCS8 } from 'jose'; if (process.env.SMOKE_ALLOW_WRITE !== '1') throw new Error('This smoke test writes synthetic records. Set SMOKE_ALLOW_WRITE=1 only for a disposable deployment.'); const url = process.env.CONVEX_SELF_HOSTED_URL!; const store = new ConvexStore(url, process.env.CONVEX_BRIDGE_SECRET!); const now = Date.now(); const hash = hashSecret(randomSecret()); const characterId = 90_000_000 + Math.floor(Math.random() * 1_000_000); const user = await store.completeLogin({ characterId, name: 'Disposable Test Pilot', ownerHash: hashSecret(randomSecret()), scopes: ['esi-assets.read_assets.v1'], intent: 'initial_login', capability: 'asset_search', requestedScopes: ['esi-assets.read_assets.v1'], approvedExistingScopes: [], encryptedTokens: 'test-only-encrypted-placeholder', tokenExpiresAt: now + 60_000, sessionHash: hash, sessionExpiresAt: now + 60_000 }); assert.equal((await store.session(hash))?.userId, user.userId); const generation = await store.beginSync(user.userId, characterId); await store.stageAssets(user.userId, characterId, generation, 0, [{ itemId: '9223372036854775806', characterId, typeId: 34, quantity: 50, locationId: '60003760', locationType: 'station', locationFlag: 'Hangar' }]); await store.finishSync(user.userId, characterId, generation, 1, now, now + 3_600_000); assert.equal((await store.assets(user.userId, characterId))[0]!.quantity, 50); let isolated = false; try { isolated = (await store.assets('other-user', characterId)).length === 0; } catch { isolated = true; } assert(isolated); const failure = await store.beginSync(user.userId, characterId); await store.stageAssets(user.userId, characterId, failure, 0, []); await store.failSync(user.userId, characterId, failure, 'test failure'); assert.equal((await store.assets(user.userId, characterId))[0]!.quantity, 50); const client = new ConvexHttpClient(url, { skipConvexDeploymentUrlCheck: true, logger: false }); client.setAuth(await applicationJwt({ ...user, issuer: process.env.APP_AUTH_ISSUER!, privateKey: process.env.APP_AUTH_PRIVATE_KEY! })); const rows = await client.query(makeFunctionReference<'query', Record, unknown[] | null>('status:characters'), {}); assert.equal(rows?.length, 1); assert(!JSON.stringify(rows).includes('encryptedTokens')); const wrongAudience = new ConvexHttpClient(url, { skipConvexDeploymentUrlCheck: true, logger: false }); const key = await importPKCS8(Buffer.from(process.env.APP_AUTH_PRIVATE_KEY!, 'base64').toString('utf8'), 'RS256'); wrongAudience.setAuth(await new SignJWT({ sid: user.sessionId }).setProtectedHeader({ alg: 'RS256', typ: 'JWT', kid: 'app-v1' }).setIssuer(process.env.APP_AUTH_ISSUER!).setSubject(user.userId).setAudience('not-eve-intelligence').setIssuedAt().setExpirationTime('5m').sign(key)); let wrongAudienceDenied = false; try { const rows = await wrongAudience.query(makeFunctionReference<'query', Record, unknown[] | null>('status:characters'), {}); wrongAudienceDenied = rows === null; } catch { wrongAudienceDenied = true; } assert(wrongAudienceDenied, 'Convex must reject a correctly signed token for another audience'); await store.deleteSession(hash); assert.equal(await store.session(hash), null); let revoked = false; try { const result = await client.query(makeFunctionReference<'query', Record, unknown[] | null>('status:characters'), {}); revoked = result === null || result.length === 0; } catch { revoked = true; } assert(revoked); console.log('Real self-hosted Convex smoke passed: login/session, snapshot promotion, failure preservation, tenant isolation, JWT-safe projection and revocation.');