Embed the Composio key in the app bundle instead of shipping cred.txt
Root cause of the recurring 401: a revoked key was hardcoded in startup-recovery.ts and onboarding-service.ts and reseeded into composio_settings on every launch, and the interim fix shipped cred.txt as a visible file under resources/. - electron.vite.config.ts reads the gitignored cred.txt at build time and injects the key into the main-process bundle as __COMPOSIO_API_KEY__; no credential file is packaged and the key is not user-visible or configurable. The renderer bundle never sees it. - Delete both hardcoded-key seeders; migration 046 purges stored api_key and never-read toolkits rows while keeping entity_id and auth_config state. - Stop shipping composio_settings in the renderer snapshot.
This commit is contained in:
1 parent
2d43950123
commit
a2de132083
16 files changed
+105
-181
No files matched your search
@@ -8,9 +8,6 @@ files:
|
||||
- out/**/*
|
||||
- package.json
|
||||
- resources/icon.png
|
||||
extraResources:
|
||||
- from: cred.txt
|
||||
to: cred.txt
|
||||
asar: true
|
||||
mac:
|
||||
category: public.app-category.business
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
|
||||
@@ -5,9 +6,26 @@ import react from "@vitejs/plugin-react";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* The Composio integration key is compiled into the main-process bundle so
|
||||
* no credential file ever ships with the app. It lives in the gitignored
|
||||
* cred.txt at the repository root (first line "Composio apikey: ak_...");
|
||||
* a checkout without the file builds an app whose connector features report
|
||||
* the integration as unconfigured. Rotation = update cred.txt, rebuild.
|
||||
*/
|
||||
function composioApiKeyForBuild(): string {
|
||||
const credPath = resolve(__dirname, "cred.txt");
|
||||
if (!existsSync(credPath)) return "";
|
||||
const firstLine = readFileSync(credPath, "utf8").split("\n")[0] ?? "";
|
||||
return firstLine.split(":").slice(1).join(":").trim();
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
define: {
|
||||
__COMPOSIO_API_KEY__: JSON.stringify(composioApiKeyForBuild()),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@shared": resolve(__dirname, "src/shared"),
|
||||
|
||||
@@ -329,9 +329,9 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
||||
const composioConnections = new ComposioConnectionService(deps.connection.db);
|
||||
const connectorGrants = new ConnectorGrantsService(deps.connection.db);
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.listComposioToolkits, async (_event, input) => {
|
||||
ipcMain.handle(IPC_CHANNELS.listComposioToolkits, async () => {
|
||||
try {
|
||||
return ok(await composioConnections.listToolkits(input.orgId));
|
||||
return ok(await composioConnections.listToolkits());
|
||||
} catch (error) {
|
||||
return fail("composio_toolkits_failed", error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import type { DatabaseSync } from "node:sqlite";
|
||||
import { AuditService } from "../services/audit-service";
|
||||
|
||||
const INTERRUPTED_MESSAGE = "Interrupted by application shutdown before completion.";
|
||||
const COMPOSIO_API_KEY = "***REMOVED***";
|
||||
const DEFAULT_COMPOSIO_TOOLKITS = ["github", "gmail", "slack", "notion", "linear", "google_calendar"];
|
||||
|
||||
/**
|
||||
* A single app instance executes runs in-process, so any task_run still
|
||||
@@ -38,23 +36,4 @@ export function recoverInterruptedWork(db: DatabaseSync): void {
|
||||
where state in ('pending', 'streaming')
|
||||
and (execution_mode = 'foreground' or external_response_id is null)
|
||||
`).run(now, INTERRUPTED_MESSAGE);
|
||||
|
||||
seedComposioForExistingOrgs(db, now);
|
||||
}
|
||||
|
||||
function seedComposioForExistingOrgs(db: DatabaseSync, now: string): void {
|
||||
const orgs = db.prepare(`
|
||||
select id from organizations
|
||||
where id not in (select org_id from composio_settings where key = 'api_key')
|
||||
`).all() as Array<{ id: string }>;
|
||||
if (orgs.length === 0) return;
|
||||
const upsert = db.prepare(`
|
||||
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
|
||||
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
|
||||
`);
|
||||
for (const org of orgs) {
|
||||
upsert.run(org.id, "api_key", COMPOSIO_API_KEY, now);
|
||||
upsert.run(org.id, "toolkits", JSON.stringify(DEFAULT_COMPOSIO_TOOLKITS), now);
|
||||
upsert.run(org.id, "entity_id", org.id, now);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* The Composio API key used to be seeded into composio_settings per
|
||||
* organization — including a revoked hardcoded key that kept resurfacing.
|
||||
* The key now ships embedded in the main-process bundle and is never
|
||||
* persisted, so stored copies (and the never-read toolkits rows) are purged.
|
||||
* entity_id and auth_config:* rows stay: connections still use them.
|
||||
*/
|
||||
export const composioKeyPurgeSchema = `
|
||||
delete from composio_settings where key in ('api_key', 'toolkits');
|
||||
`;
|
||||
@@ -44,6 +44,7 @@ import { approvalNotesMeetingsSprintsSchema } from "./migrations/042-approval-no
|
||||
import { composioIntegrationSchema } from "./migrations/043-composio-integration";
|
||||
import { mentionWakeStateSchema } from "./migrations/044-mention-wake-state";
|
||||
import { connectorGrantsSchema } from "./migrations/045-connector-grants";
|
||||
import { composioKeyPurgeSchema } from "./migrations/046-composio-key-purge";
|
||||
|
||||
interface Migration {
|
||||
version: number;
|
||||
@@ -97,6 +98,7 @@ const MIGRATIONS: Migration[] = [
|
||||
{ version: 43, name: "composio_integration", sql: composioIntegrationSchema },
|
||||
{ version: 44, name: "mention_wake_state", sql: mentionWakeStateSchema },
|
||||
{ version: 45, name: "connector_grants", sql: connectorGrantsSchema },
|
||||
{ version: 46, name: "composio_key_purge", sql: composioKeyPurgeSchema },
|
||||
];
|
||||
|
||||
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Build-time constants injected by electron.vite.config.ts (`define`).
|
||||
* Vitest runs without the bundler, so the identifier may not exist at
|
||||
* runtime — always read it through a `typeof` guard.
|
||||
*/
|
||||
declare const __COMPOSIO_API_KEY__: string | undefined;
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { ComposioToolkitSummaryDto, ConnectComposioToolkitResult } from "../../../shared/commands";
|
||||
import { ComposioClient, type ComposioToolkitSummary } from "./composio-client";
|
||||
import { resolveComposioApiKey } from "./composio-credentials";
|
||||
import { composioApiKey } from "./composio-credentials";
|
||||
import { getComposioUserId } from "./composio-tool-bridge";
|
||||
import { normalizeSlug } from "./connector-grants";
|
||||
|
||||
@@ -18,8 +18,8 @@ let catalogCache: { fetchedAt: number; toolkits: ComposioToolkitSummary[] } | nu
|
||||
export class ComposioConnectionService {
|
||||
constructor(private readonly db: DatabaseSync) {}
|
||||
|
||||
listToolkits(orgId: string): Promise<ComposioToolkitSummaryDto[]> {
|
||||
const client = this.requireClient(orgId);
|
||||
listToolkits(): Promise<ComposioToolkitSummaryDto[]> {
|
||||
const client = this.requireClient();
|
||||
return this.catalog(client);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ComposioConnectionService {
|
||||
*/
|
||||
async connectToolkit(orgId: string, toolkitSlug: string, toolkitName: string): Promise<ConnectComposioToolkitResult> {
|
||||
const slug = normalizeSlug(toolkitSlug);
|
||||
const client = this.requireClient(orgId);
|
||||
const client = this.requireClient();
|
||||
const authConfigId = await this.ensureAuthConfig(client, orgId, slug);
|
||||
const account = await client.createConnectedAccount({
|
||||
authConfigId,
|
||||
@@ -54,7 +54,7 @@ export class ComposioConnectionService {
|
||||
|
||||
/** Pulls account statuses from Composio and updates the local mirror. */
|
||||
async refreshAccounts(orgId: string): Promise<void> {
|
||||
const client = this.requireClient(orgId);
|
||||
const client = this.requireClient();
|
||||
const accounts = await client.listConnectedAccounts(getComposioUserId(this.db, orgId));
|
||||
const now = new Date().toISOString();
|
||||
for (const account of accounts) {
|
||||
@@ -80,9 +80,9 @@ export class ComposioConnectionService {
|
||||
}
|
||||
}
|
||||
|
||||
private requireClient(orgId: string): ComposioClient {
|
||||
const apiKey = resolveComposioApiKey(this.db, orgId);
|
||||
if (!apiKey) throw new Error("Composio API key is not configured for this company.");
|
||||
private requireClient(): ComposioClient {
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) throw new Error("The Composio integration is not configured in this build.");
|
||||
return new ComposioClient(apiKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,17 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
/**
|
||||
* The Composio integration key ships with the product: cred.txt (first line
|
||||
* "Composio apikey: ak_...", gitignored) sits at the project root in
|
||||
* development and inside the packaged app's resources directory in
|
||||
* production. Wherever the file exists it is the source of truth — rotating
|
||||
* the key in the file replaces whatever the database held on the next
|
||||
* resolution. It is not user-configurable.
|
||||
* The Composio integration key ships inside the compiled main-process
|
||||
* bundle: electron.vite.config.ts reads the gitignored cred.txt at the
|
||||
* repository root during build and injects the key as __COMPOSIO_API_KEY__.
|
||||
* No credential file is packaged, nothing is persisted to the database, and
|
||||
* the key is never user-visible or configurable. Rotating the key means
|
||||
* updating cred.txt and rebuilding.
|
||||
*/
|
||||
export function resolveComposioApiKey(db: DatabaseSync, orgId: string, credentialFileDirs: string[] = defaultCredentialDirs()): string | null {
|
||||
const stored = db.prepare(
|
||||
"select value from composio_settings where org_id = ? and key = 'api_key' limit 1",
|
||||
).get(orgId) as { value: string } | undefined;
|
||||
|
||||
const fileKey = readCredentialFile(credentialFileDirs);
|
||||
if (!fileKey) return stored?.value ?? null;
|
||||
if (stored?.value === fileKey) return fileKey;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const upsert = db.prepare(`
|
||||
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
|
||||
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
|
||||
`);
|
||||
upsert.run(orgId, "api_key", fileKey, now);
|
||||
upsert.run(orgId, "entity_id", orgId, now);
|
||||
return fileKey;
|
||||
export function composioApiKey(): string | null {
|
||||
return normalizeComposioApiKey(typeof __COMPOSIO_API_KEY__ === "string" ? __COMPOSIO_API_KEY__ : "");
|
||||
}
|
||||
|
||||
/** Project root in dev; the app's resources directory when packaged. */
|
||||
export function defaultCredentialDirs(): string[] {
|
||||
const dirs = [process.cwd()];
|
||||
if (process.resourcesPath) dirs.push(process.resourcesPath);
|
||||
return dirs;
|
||||
}
|
||||
|
||||
function readCredentialFile(dirs: string[]): string | null {
|
||||
for (const dir of dirs) {
|
||||
const path = join(dir, "cred.txt");
|
||||
if (!existsSync(path)) continue;
|
||||
const firstLine = readFileSync(path, "utf8").split("\n")[0] ?? "";
|
||||
const key = firstLine.split(":").slice(1).join(":").trim();
|
||||
if (key) return key;
|
||||
}
|
||||
return null;
|
||||
/** Null for blank input so callers surface "not configured" explicitly. */
|
||||
export function normalizeComposioApiKey(raw: string): string | null {
|
||||
const key = raw.trim();
|
||||
return key ? key : null;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { ToolDefinition, ToolCall } from "../provider-tool-turn";
|
||||
import { ComposioClient, type ComposioToolSchema } from "./composio-client";
|
||||
import { resolveComposioApiKey } from "./composio-credentials";
|
||||
import { composioApiKey } from "./composio-credentials";
|
||||
import { ConnectorGrantsService } from "./connector-grants";
|
||||
|
||||
const COMPOSIO_TOOL_PREFIX = "composio_";
|
||||
@@ -53,7 +53,7 @@ export function composioSchemaToToolDefinition(schema: ComposioToolSchema): Tool
|
||||
* granted this agent.
|
||||
*/
|
||||
export async function loadComposioTools(db: DatabaseSync, orgId: string, agentId: string): Promise<ToolDefinition[]> {
|
||||
const apiKey = getComposioApiKey(db, orgId);
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) return [];
|
||||
const grants = new ConnectorGrantsService(db);
|
||||
const toolkits = grants.toolkitsForAgent(orgId, agentId);
|
||||
@@ -84,8 +84,8 @@ export async function executeComposioTool(
|
||||
agentId: string,
|
||||
call: ToolCall,
|
||||
): Promise<string> {
|
||||
const apiKey = getComposioApiKey(db, orgId);
|
||||
if (!apiKey) return "ERROR: Composio API key is not configured for this company.";
|
||||
const apiKey = composioApiKey();
|
||||
if (!apiKey) return "ERROR: The Composio integration is not configured in this build.";
|
||||
const toolSlug = originalToolName(call.name);
|
||||
const toolkit = toolkitForTool(toolSlug);
|
||||
const grants = new ConnectorGrantsService(db);
|
||||
@@ -114,10 +114,6 @@ export async function executeComposioTool(
|
||||
}
|
||||
}
|
||||
|
||||
function getComposioApiKey(db: DatabaseSync, orgId: string): string | null {
|
||||
return resolveComposioApiKey(db, orgId);
|
||||
}
|
||||
|
||||
export function getComposioUserId(db: DatabaseSync, orgId: string): string {
|
||||
const row = db.prepare(
|
||||
"select value from composio_settings where org_id = ? and key = 'entity_id' limit 1",
|
||||
|
||||
@@ -86,7 +86,6 @@ export class OnboardingService {
|
||||
});
|
||||
const runId = this.createTaskRun(orgId, taskId, ceoAgentId, providerId, workspaceId, now);
|
||||
|
||||
this.seedComposioSettings(orgId, now);
|
||||
|
||||
this.audit.record({
|
||||
orgId,
|
||||
@@ -235,24 +234,8 @@ export class OnboardingService {
|
||||
return row.id;
|
||||
}
|
||||
|
||||
private seedComposioSettings(orgId: string, now: string): void {
|
||||
const existing = this.deps.db.prepare(
|
||||
"select value from composio_settings where org_id = ? and key = 'api_key'",
|
||||
).get(orgId) as { value: string } | undefined;
|
||||
if (existing) return;
|
||||
const upsert = this.deps.db.prepare(`
|
||||
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
|
||||
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
|
||||
`);
|
||||
upsert.run(orgId, "api_key", COMPOSIO_API_KEY, now);
|
||||
upsert.run(orgId, "toolkits", JSON.stringify(DEFAULT_COMPOSIO_TOOLKITS), now);
|
||||
upsert.run(orgId, "entity_id", orgId, now);
|
||||
}
|
||||
}
|
||||
|
||||
const COMPOSIO_API_KEY = "***REMOVED***";
|
||||
const DEFAULT_COMPOSIO_TOOLKITS = ["github", "gmail", "slack", "notion", "linear", "google_calendar"];
|
||||
|
||||
function projectName(input: BootstrapOnboardingInput): string {
|
||||
return `Execution plan: ${input.goalTitle.trim()}`;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,6 @@ export class SnapshotService {
|
||||
.map((rule) => ({ ...rule, enabled: toBool(rule.enabled) })),
|
||||
meetings: list(this.db, "meetings", "scheduled_at desc"),
|
||||
sprints: list(this.db, "sprints", "start_date desc"),
|
||||
composioSettings: list(this.db, "composio_settings", "org_id asc, key asc"),
|
||||
connectorAccounts: list(this.db, "connector_accounts", "toolkit_slug asc"),
|
||||
connectorGrants: list(this.db, "agent_connector_grants", "toolkit_slug asc, created_at asc"),
|
||||
};
|
||||
|
||||
@@ -140,14 +140,6 @@ export interface AppSnapshot {
|
||||
automationRules: AutomationRuleRecord[];
|
||||
meetings: MeetingRecord[];
|
||||
sprints: SprintRecord[];
|
||||
composioSettings: ComposioSettingRecord[];
|
||||
connectorAccounts: ConnectorAccountRecord[];
|
||||
connectorGrants: ConnectorGrantRecord[];
|
||||
}
|
||||
|
||||
export interface ComposioSettingRecord {
|
||||
orgId: string;
|
||||
key: string;
|
||||
value: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -1,78 +1,19 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
import { resolveComposioApiKey } from "../../src/main/services/composio/composio-credentials";
|
||||
|
||||
interface Harness {
|
||||
connection: DatabaseConnection;
|
||||
orgId: string;
|
||||
credDir: string;
|
||||
}
|
||||
|
||||
function harness(): Harness {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-cred-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
});
|
||||
const credDir = join(tmpdir(), `ac-cred-dir-${randomUUID()}`);
|
||||
mkdirSync(credDir, { recursive: true });
|
||||
return { connection, orgId, credDir };
|
||||
}
|
||||
|
||||
function storedKey(h: Harness): string | null {
|
||||
const row = h.connection.db.prepare(
|
||||
"select value from composio_settings where org_id = ? and key = 'api_key'",
|
||||
).get(h.orgId) as { value: string } | undefined;
|
||||
return row?.value ?? null;
|
||||
}
|
||||
|
||||
function writeCred(h: Harness, key: string): void {
|
||||
writeFileSync(join(h.credDir, "cred.txt"), `Composio apikey: ${key}\nProject id: pr_x\n`, "utf8");
|
||||
}
|
||||
import { composioApiKey, normalizeComposioApiKey } from "../../src/main/services/composio/composio-credentials";
|
||||
|
||||
describe("composio credential resolution", () => {
|
||||
test("seeds the database from the credential file", () => {
|
||||
const h = harness();
|
||||
writeCred(h, "ak_first");
|
||||
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_first");
|
||||
expect(storedKey(h)).toBe("ak_first");
|
||||
test("trims the embedded key", () => {
|
||||
expect(normalizeComposioApiKey(" ak_live ")).toBe("ak_live");
|
||||
});
|
||||
|
||||
test("a rotated credential file replaces the stale stored key", () => {
|
||||
const h = harness();
|
||||
writeCred(h, "ak_old");
|
||||
resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir]);
|
||||
writeCred(h, "ak_rotated");
|
||||
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_rotated");
|
||||
expect(storedKey(h)).toBe("ak_rotated");
|
||||
test("a blank key resolves to null so callers report the integration as unconfigured", () => {
|
||||
expect(normalizeComposioApiKey("")).toBeNull();
|
||||
expect(normalizeComposioApiKey(" ")).toBeNull();
|
||||
});
|
||||
|
||||
test("finds the credential file in a later directory when the first has none", () => {
|
||||
const h = harness();
|
||||
writeCred(h, "ak_resources");
|
||||
const emptyDir = join(tmpdir(), `ac-cred-empty-${randomUUID()}`);
|
||||
mkdirSync(emptyDir, { recursive: true });
|
||||
expect(resolveComposioApiKey(h.connection.db, h.orgId, [emptyDir, h.credDir])).toBe("ak_resources");
|
||||
expect(storedKey(h)).toBe("ak_resources");
|
||||
});
|
||||
|
||||
test("without a credential file the stored key stands", () => {
|
||||
const h = harness();
|
||||
const now = new Date().toISOString();
|
||||
h.connection.db.prepare(
|
||||
"insert into composio_settings(org_id, key, value, updated_at) values (?, 'api_key', 'ak_settings', ?)",
|
||||
).run(h.orgId, now);
|
||||
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_settings");
|
||||
});
|
||||
|
||||
test("no file and no stored key yields null", () => {
|
||||
const h = harness();
|
||||
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBeNull();
|
||||
test("without the build-time injection the key is null", () => {
|
||||
// Vitest runs without the electron-vite define — the same shape as a
|
||||
// build from a checkout that has no cred.txt.
|
||||
expect(composioApiKey()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { composioKeyPurgeSchema } from "../../src/main/database/migrations/046-composio-key-purge";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
|
||||
describe("composio key purge migration", () => {
|
||||
test("removes stored api_key and toolkits rows but keeps connection state", () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-purge-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
});
|
||||
const now = new Date().toISOString();
|
||||
const insert = connection.db.prepare(
|
||||
"insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)",
|
||||
);
|
||||
insert.run(orgId, "api_key", "ak_revoked", now);
|
||||
insert.run(orgId, "toolkits", '["github"]', now);
|
||||
insert.run(orgId, "entity_id", orgId, now);
|
||||
insert.run(orgId, "auth_config:github", "ac_123", now);
|
||||
|
||||
connection.db.exec(composioKeyPurgeSchema);
|
||||
|
||||
const keys = (connection.db.prepare("select key from composio_settings where org_id = ? order by key asc").all(orgId) as Array<{ key: string }>)
|
||||
.map((row) => row.key);
|
||||
expect(keys).toEqual(["auth_config:github", "entity_id"]);
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user