Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/alerts/keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
// --- 1. CORE IMPLEMENTATION CODE ---
// ==========================================

import { KeychainStore, SecureKeypairStore, KeysCliController } from "./keys";
import { SecureKeypairStore, KeysCliController } from "./keys";

// ==========================================
// --- 2. TDD AUTOMATED TEST SUITE ---
Expand All @@ -23,11 +23,11 @@ describe("TDD - Local OS Keychain & Safe CLI Key Management Engine", () => {

// Step 1: Build robust spy mocks matching native keytar interfaces
mockKeytar = {
setPassword: vi.fn().mockImplementation(async (service, account, password) => {
setPassword: vi.fn().mockImplementation(async (_service, account, password) => {
fakeKeychainDb.push({ account, value: password });
return null;
}),
findCredentials: vi.fn().mockImplementation(async (service) => {
findCredentials: vi.fn().mockImplementation(async () => {
return fakeKeychainDb.map(item => ({ account: item.account, password: item.value }));
})
};
Expand Down
28 changes: 0 additions & 28 deletions src/core/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,34 +261,6 @@ export async function runBatchDiscovery(

// ─── Private helpers ──────────────────────────────────────────────────────────

/**
* Attempt to build a contract data ledger key from a contract ID and an XDR value.
* Returns null if the construction fails.
*/
function buildContractDataKey(
contractId: string,
keyVal: xdr.ScVal,
): xdr.LedgerKey | null {
try {
const raw = Buffer.from(contractId, "hex").length === 32
? Buffer.from(contractId, "hex")
: decodeContractId(contractId);
const contractAddress = xdr.ScAddress.scAddressTypeContract(
raw as unknown as xdr.Hash,
);

return xdr.LedgerKey.contractData(
new xdr.LedgerKeyContractData({
contract: contractAddress,
key: keyVal,
durability: xdr.ContractDataDurability.persistent(),
}),
);
} catch {
return null;
}
}

/**
* Decode a Stellar contract ID (C...) to raw 32-byte buffer.
*/
Expand Down
64 changes: 43 additions & 21 deletions src/db/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ const SCHEMA = fs.readFileSync(SCHEMA_FILE_PATH, 'utf-8')

let db: Database.Database | null = null;

const LIVE_MIGRATIONS = [
`ALTER TABLE alerts_fired ADD COLUMN delivered INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE alerts_fired ADD COLUMN delivered_at TEXT`,
`ALTER TABLE alerts_fired ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE alert_configs ADD COLUMN webhook_secret TEXT`,
`ALTER TABLE contracts ADD COLUMN poll_interval_seconds INTEGER`,
`CREATE TABLE IF NOT EXISTS channel_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
public_key TEXT NOT NULL UNIQUE,
keypair_source TEXT,
label TEXT,
network TEXT NOT NULL DEFAULT 'testnet',
funded BOOLEAN NOT NULL DEFAULT 0,
balance_xlm REAL,
balance_checked_at TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)`,
`ALTER TABLE contracts ADD COLUMN last_introspected_at DATETIME`,
];

export function getDatabase(customPath?: string): Database.Database {
if (db) return db;

Expand All @@ -45,27 +65,17 @@ export function getDatabase(customPath?: string): Database.Database {
// ALTER TABLE is idempotent-safe here: we catch the "duplicate column" error
// that SQLite throws when the column already exists. This handles existing
// sorokeep.db files created before these columns were added to schema.sql.
const migrations = [
`ALTER TABLE alerts_fired ADD COLUMN delivered INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE alerts_fired ADD COLUMN delivered_at TEXT`,
`ALTER TABLE alerts_fired ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE alert_configs ADD COLUMN webhook_secret TEXT`,
`ALTER TABLE contracts ADD COLUMN poll_interval_seconds INTEGER`,
`CREATE TABLE IF NOT EXISTS channel_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
public_key TEXT NOT NULL UNIQUE,
keypair_source TEXT,
label TEXT,
network TEXT NOT NULL DEFAULT 'testnet',
funded BOOLEAN NOT NULL DEFAULT 0,
balance_xlm REAL,
balance_checked_at TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)`,
`ALTER TABLE contracts ADD COLUMN last_introspected_at DATETIME`,
];
for (const sql of migrations) {
try { db.exec(sql); } catch { /* column already exists — no-op */ }
for (const sql of LIVE_MIGRATIONS) {
try {
db.exec(sql);
} catch (err: unknown) {
// Only ignore duplicate-column errors; rethrow other problems
if (err instanceof Error && /duplicate column name/i.test(err.message)) {
/* no-op (already exists) */
} else {
throw err;
}
}
}
Comment thread
Stephan-Thomas marked this conversation as resolved.

migrateAlertConfigsChannelTypeCheck(db);
Expand Down Expand Up @@ -145,5 +155,17 @@ export function getDatabaseForTesting(): Database.Database {
const migrator = new Migrator(db, migrationsDir);
migrator.run();

for (const sql of LIVE_MIGRATIONS) {
try {
db.exec(sql);
} catch (err: unknown) {
if (err instanceof Error && /duplicate column name/i.test(err.message)) {
/* ignore */
} else {
throw err;
}
}
}

return db;
}
2 changes: 0 additions & 2 deletions src/rpc/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
Operation,
Keypair,
SorobanDataBuilder,
FeeBumpTransaction,
Asset,
} from "@stellar/stellar-sdk";
import { getLogger } from "../logging/index.js";
import { CostSummary } from "../core/costs.js";

export function assertSimulationSuccess(sim: rpc.Api.SimulateTransactionResponse): asserts sim is rpc.Api.SimulateTransactionSuccessResponse {
if (rpc.Api.isSimulationError(sim)) {
Expand Down Expand Up @@ -794,7 +792,7 @@
});

for (const dest of destinations) {
builder.addOperation(

Check failure on line 795 in src/rpc/client.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22.x)

Duplicate name 'submitRestore'
Operation.payment({
destination: dest.publicKey,
asset: Asset.native(),
Expand Down
4 changes: 2 additions & 2 deletions tests/commands/costs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ function seedBasicData(db: Database.Database, costXlm = 0.001) {

describe("costs command — Forecasted Rent section", () => {
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;

let exitSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
mockDb = getDatabaseForTesting();
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});
exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
throw new Error("process.exit called");
});
Expand Down
1 change: 0 additions & 1 deletion tests/core/monitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
insertAlertConfig,
getAlertConfigsForContract,
hasUnresolvedAlert,
upsertExtensionPolicy,
} from "../../src/db/repositories.js";
import {getDatabaseForTesting} from "../../src/db/database";
import {MonitorCycleResult, runMonitorCycle} from "../../src/core/monitor";
Expand Down
3 changes: 1 addition & 2 deletions tests/core/rate_limiter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
insertContract,
upsertEntry,
upsertExtensionPolicy,
recordExtension,
} from "../../src/db/repositories.js";

// ─── Mock RPC client ─────────────────────────────────────────────────────────
Expand Down Expand Up @@ -279,7 +278,7 @@ describe("runAutoExtensions — rate limiting", () => {
const contract2 = "COTHER111111111111111111111111111111111111111111111111111";

const entryId1 = seedContractAndPolicy(db, CONTRACT_ID);
const entryId2 = seedContractAndPolicy(db, contract2);
seedContractAndPolicy(db, contract2);

// Saturate contract1 only
insertRecentExtensions(db, CONTRACT_ID, entryId1, HOURLY_RATE_LIMIT);
Expand Down
1 change: 0 additions & 1 deletion tests/db/rate_limiter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { getDatabaseForTesting } from "../../src/db/database.js";
import {
insertContract,
upsertEntry,
recordExtension,
countExtensionsInLastHour,
} from "../../src/db/repositories.js";

Expand Down
1 change: 0 additions & 1 deletion tests/e2e/sandbox-network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type Database from "better-sqlite3";
import { getDatabaseForTesting } from "../../src/db/database.js";
import { watchContract } from "../../src/core/watch.js";
import { runMonitorCycle } from "../../src/core/monitor.js";
import { runAutoExtensions } from "../../src/core/extension.js";
import {
getAlertHistory,
getEntriesForContract,
Expand Down
1 change: 0 additions & 1 deletion tests/rpc/resource_estimate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import { describe, it, expect } from "vitest";
import {
parseResourceEstimate,
type ResourceEstimate,
} from "../../src/rpc/client.js";

// ─── Mock simulation response shapes ─────────────────────────────────────────
Expand Down
Loading