Skip to main content
Glama
test_helpers.ts12.9 kB
import { SdfApiClient } from "./sdf_api_client.ts"; import assert from "node:assert"; export function sleep(ms: number) { const natural_ms = Math.max(0, Math.floor(ms)); console.log(`Sleeping for ${natural_ms} ms`); return new Promise((resolve) => setTimeout(resolve, natural_ms)); } export function sleepBetween(minMs: number, maxMs: number) { if (maxMs < minMs) maxMs = minMs; return sleep(minMs + Math.floor((maxMs - minMs) * Math.random())); } export async function retryUntil( fn: () => Promise<void>, timeoutMs: number, message?: string, retries = 20, backoffFactor = 1.2, // exponential backoff factor initialDelay = 0.4, // in seconds ): Promise<void> { const retryPromise = retryWithBackoff( fn, retries, backoffFactor, initialDelay, ); const timeoutPromise = new Promise<void>((_, reject) => { setTimeout(() => { reject( new Error( `Timeout after ${timeoutMs}ms while retrying operation: ${message}`, ), ); }, timeoutMs); }); return Promise.race([retryPromise, timeoutPromise]); } // Run fn n times, with increasing intervals between tries export async function retryWithBackoff( fn: () => Promise<void>, retries = 60, backoffFactor = 1.2, initialDelay = 2, /// in seconds ) { const maxDelay = 10; let latest_err; let try_count = 0; let delay = initialDelay; console.log("Running retry_with_backoff block"); do { try_count++; latest_err = undefined; console.log(`try number ${try_count}`); try { await fn(); } catch (e) { latest_err = e; await sleep(delay * 1000); delay = Math.min(delay * backoffFactor, maxDelay); } } while (latest_err && try_count < retries); if (latest_err) { throw latest_err; } } export async function runWithTemporaryChangeset( sdf: SdfApiClient, fn: (sdf: SdfApiClient, changesetId: string) => Promise<void>, ) { // CREATE CHANGESET const startTime = new Date(); const changeSetName = `API_TEST - ${fn.name} - ${startTime.toISOString()}`; const data = await sdf.call({ route: "create_change_set", body: { name: changeSetName, }, }); assert(typeof data === "object", "Expected changeSet in response"); const changeSet = data; assert(changeSet?.id, "Expected Change Set id"); assert( changeSet?.name === changeSetName, `Changeset name should be ${changeSetName}`, ); const changeSetId = changeSet.id; console.log(`Created temporary changeset ${changeSetId}`); await sleepBetween(1000, 5000); console.log("Fetching changeset index before running fn"); // FETCH CHANGESET INDEX before running fn so we ensure the index has been built await sdf.fetchChangeSetIndex(changeSetId); // RUN FN let err; try { await fn(sdf, changeSetId); } catch (e) { err = e; } // DELETE CHANGE SET await sdf.call({ route: "abandon_change_set", routeVars: { workspaceId: sdf.workspaceId, changeSetId, }, }); if (err) { throw err; } } export const nilId = "00000000000000000000000000"; // Various Helpers ------------------------------------------------------------ export async function getFuncs(sdf: SdfApiClient, changeSetId: string) { return await sdf.call({ route: "func_list", routeVars: { workspaceId: sdf.workspaceId, changeSetId, }, }); } export async function installModule( sdf: SdfApiClient, changeSetId: string, moduleId: string, ) { const installModulePayload = { visibility_change_set_pk: changeSetId, ids: [moduleId], }; return await sdf.call({ route: "install_module", body: installModulePayload, }); } // Schema Variant Helpers ------------------------------------------------------------ export async function createAsset( sdf: SdfApiClient, changeSetId: string, name: string, ): Promise<string> { const createAssetPayload = { visibility_change_set_pk: changeSetId, name, color: "#AAFF00", }; const createResp = await sdf.call({ route: "create_variant", body: createAssetPayload, }); const schemaVariantId = createResp?.schemaVariantId; assert(schemaVariantId, "Expected to get a schema variant id after creation"); return schemaVariantId; } export async function updateAssetCode( sdf: SdfApiClient, changeSetId: string, schemaVariantId: string, newCode: string, ): Promise<string> { // Get variant const variant = await sdf.call({ route: "get_variant", routeVars: { workspaceId: sdf.workspaceId, changeSetId, schemaVariantId, }, }); // update variant const updateVariantBody = { visibility_change_set_pk: changeSetId, code: newCode, variant, }; const saveResp = await sdf.call({ route: "save_variant", body: updateVariantBody, }); const success = saveResp.success; assert(success, "save was successful"); const regenBody = { visibility_change_set_pk: changeSetId, variant, }; const regenerateResp = await sdf.call({ route: "regenerate_variant", body: regenBody, }); const maybeNewId = regenerateResp.schemaVariantId; assert(maybeNewId, "Expected to get a schema variant id after regenerate"); return maybeNewId; } // Component Helpers ------------------------------------------------------------ export async function createComponent( sdf: SdfApiClient, changeSetId: string, viewId: string, request: any, ): Promise<string> { // Create a Component const createComponentResp = await sdf.call({ route: "create_component_v2", routeVars: { workspaceId: sdf.workspaceId, changeSetId, viewId: viewId, }, body: request, }); const { newComponentId, newComponentName } = { newComponentId: createComponentResp?.componentId, newComponentName: createComponentResp?.materializedView?.name, }; assert(newComponentId, "Expected to get a component id after creation"); // Wait for and verify component MV await eventualMVAssert( sdf, changeSetId, "Component", newComponentId, (mv) => mv.name === newComponentName, "Component MV should exist and have matching name", ); return newComponentId; } // Func Helpers ------------------------------------------------------------ export async function getFuncRun( sdf: SdfApiClient, changeSetId: string, funcRunId: string, ) { console.log(funcRunId); const funcRun = await sdf.call({ route: "get_func_run", routeVars: { workspaceId: sdf.workspaceId, changeSetId, funcRunId, }, }); return funcRun; } export async function testExecuteFunc( sdf: SdfApiClient, changeSetId: string, funcId: string, componentId: string, code: string, args: any, ) { const testExecuteFuncPayload = { args, code, componentId, }; const createFuncResp = await sdf.call({ route: "test_execute", routeVars: { workspaceId: sdf.workspaceId, changeSetId, funcId, }, body: testExecuteFuncPayload, }); // returns the func_run_id return createFuncResp; } export async function createQualification( sdf: SdfApiClient, changeSetId: string, name: string, schemaVariantId: string, code: string, ) { const createFuncPayload = { name, displayName: name, description: "", binding: { funcId: nilId, schemaVariantId, bindingKind: "qualification", inputs: [], }, kind: "Qualification", requestUlid: changeSetId, }; const createFuncResp = await sdf.call({ route: "create_func", routeVars: { workspaceId: sdf.workspaceId, changeSetId, }, body: createFuncPayload, }); // now list funcs and let's make sure we see it const funcs = await sdf.call({ route: "func_list", routeVars: { workspaceId: sdf.workspaceId, changeSetId, }, }); const createdFunc = funcs.find((f) => f.name === name); assert(createdFunc, "Expected to find newly created func"); const funcId = createdFunc.funcId; const codePayload = { code, requestUlid: changeSetId, }; // save the code const updateFuncResp = await sdf.call({ route: "update_func_code", routeVars: { workspaceId: sdf.workspaceId, changeSetId, funcId, }, body: codePayload, }); return funcId; } // MV Helpers ------------------------------------------------------- export async function abandon_all_change_sets(sdf: SdfApiClient) { const workspaceId = sdf.workspaceId; const data = await sdf.call({ route: "list_open_change_sets", routeVars: { workspaceId, }, }); // loop through them and abandon each one that's not head assert(data.defaultChangeSetId, "Expected headChangeSetId"); const changeSetsToAbandon = data.changeSets.filter( (c) => c.id !== data.defaultChangeSetId, ); for (const changeSet of changeSetsToAbandon) { const changeSetId = changeSet.id; await sdf.call({ route: "abandon_change_set", routeVars: { workspaceId: sdf.workspaceId, changeSetId, }, }); } } export async function getActions(sdf: SdfApiClient, changeSetId: string) { const actions = await sdf.changeSetMjolnir( changeSetId, "ActionViewList", sdf.workspaceId, ); assert(actions, "Expected to get actions MV"); return actions; } // Used to generate the correct payload for sdf // If not given a Schema Id, assume it's a builtin, and find // the Id by the name by looking at the CachedSchemas Deployment MV // If given a schema Id, just try to find an installed one export async function createComponentPayload( sdf: SdfApiClient, changeSetId: string, schemaName: string, schemaId?: string, ) { const deploymentIndex = await sdf.fetchDeploymentIndex(); // console.log(deploymentIndex); const cachedSchemasMV = deploymentIndex.frontEndObject.data.mvList.find( (mv: any) => mv.kind === "CachedSchemas", ); const cachedSchemasMVId = cachedSchemasMV.id; let actualSchemaId = undefined; if (!schemaId) { // Get the builtin version, so we can get the Schema Id as we only have the name const builtins = await sdf.deploymentMjolnir( "CachedSchemas", cachedSchemasMVId, ); assert(builtins, "Expected to get schemas MV"); let foundSchema = builtins.schemas.find( (schema: { id: string; name: string }) => schema.name === schemaName, ); assert(foundSchema, `Expected to find id for Schema Name ${schemaName}`); actualSchemaId = foundSchema.id; assert(actualSchemaId, `Expected to find id for Schema Name ${schemaName}`); } else { actualSchemaId = schemaId; } assert(actualSchemaId, "we have a valid schema Id!"); try { // First is it installed? const maybeInstalled = await sdf.changeSetMjolnir( changeSetId, "LuminorkDefaultVariant", actualSchemaId, ); // if so, return it return { schemaVariantId: maybeInstalled.variantId, x: "0", y: "0", height: "0", width: "0", parentId: null, schemaType: "installed", }; } catch (e) { console.log( `LuminorkDefaultVariant not found for ${schemaName}, assuming it's uninstalled`, ); return { schemaId: actualSchemaId, schemaType: "uninstalled", x: "0", y: "0", height: "0", width: "0", parentId: null, }; } } export async function getViews(sdf: SdfApiClient, changeSetId: string) { const viewsList = await sdf.changeSetMjolnir( changeSetId, "ViewList", sdf.workspaceId, ); assert(viewsList, "Expected to get views MV"); let viewsToFetch = viewsList.views; let views = await sdf.changeSetMultiMjolnir(changeSetId, viewsToFetch); assert(views, "Expected to get views data"); return views; } export async function eventualMVAssert( sdf: SdfApiClient, changeSetId: string, kind: string, id: string, assertFn: (mv: any) => boolean, message: string, timeoutMs: number = 90000, // 90 seconds which should be plenty of time, but we're seeing some timing issues in crons so bumping it up dramatically ): Promise<void> { // update this to use sdf.mjolnir and retryUntil if (!sdf || !changeSetId || !kind || !id) { throw new Error("Invalid parameters for eventualAssert"); } await retryUntil( async () => { const mv = await sdf.changeSetMjolnir(changeSetId, kind, id); if (mv) { try { if (assertFn(mv)) { return; // Success! } else { throw new Error(`Assertion failed for ${kind} with ID ${id}`); } } catch (error) { // Continue polling if assertion throws throw error; // Re-throw to trigger retry } } else { throw new Error(`No MV found for ${kind} with ID ${id}`); } }, timeoutMs, message, ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server