From 208d191ca035659303dcaf4d1be442f9aa24670a Mon Sep 17 00:00:00 2001 From: Abhinav Pappu Date: Fri, 26 Jun 2026 18:21:08 +0000 Subject: [PATCH] Add integration tests for dataset, ingest-token, datastream, and skill commands Adds setup helpers (`setup.ts`) to seed fixtures via API and cleanup helpers (`cleanup.ts`) to tear them down via GQL/REST, both registered automatically through `fixture.registerCleanup()`. Extends `fixture.ts` with async cleanup, `retryUntil` for polling newly materialized resources, and `CleanupFn` support. - Dataset: list with exact label filter, view field validation, query with `make_col` pipeline assertion - Ingest token: full CRUD including `--disabled` / `--no-disabled` toggle with post-update view checks - Datastream: CRUD with post-update view (CI-only) - Skill: list/view/`--content` on REST-created fixtures Made-with: Cursor --- integration/README.md | 19 ++- integration/cleanup.ts | 42 ++++++ integration/dataset.test.ts | 85 ++++++++++++ integration/datastream.test.ts | 85 ++++++++++++ integration/fixture.test.ts | 6 +- integration/fixture.ts | 48 ++++++- integration/ingest-token.test.ts | 125 ++++++++++++++++++ integration/setup.ts | 92 +++++++++++++ integration/skill.test.ts | 67 ++++++++++ integration/smoke.test.ts | 4 +- src/gql/dataset/delete-dataset.graphql | 6 + src/gql/dataset/delete-dataset.ts | 22 +++ src/gql/dataset/save-dataset.graphql | 13 ++ src/gql/dataset/save-dataset.ts | 18 +++ src/gql/datastream/delete-datastream.graphql | 6 + src/gql/datastream/delete-datastream.ts | 18 +++ .../ingest-token/delete-ingest-token.graphql | 6 + src/gql/ingest-token/delete-ingest-token.ts | 18 +++ src/rest/skill/create-skill.ts | 14 ++ src/rest/skill/delete-skill.ts | 13 ++ 20 files changed, 695 insertions(+), 12 deletions(-) create mode 100644 integration/cleanup.ts create mode 100644 integration/dataset.test.ts create mode 100644 integration/datastream.test.ts create mode 100644 integration/ingest-token.test.ts create mode 100644 integration/setup.ts create mode 100644 integration/skill.test.ts create mode 100644 src/gql/dataset/delete-dataset.graphql create mode 100644 src/gql/dataset/delete-dataset.ts create mode 100644 src/gql/dataset/save-dataset.graphql create mode 100644 src/gql/dataset/save-dataset.ts create mode 100644 src/gql/datastream/delete-datastream.graphql create mode 100644 src/gql/datastream/delete-datastream.ts create mode 100644 src/gql/ingest-token/delete-ingest-token.graphql create mode 100644 src/gql/ingest-token/delete-ingest-token.ts create mode 100644 src/rest/skill/create-skill.ts create mode 100644 src/rest/skill/delete-skill.ts diff --git a/integration/README.md b/integration/README.md index ea6267d..c5ef65c 100644 --- a/integration/README.md +++ b/integration/README.md @@ -46,9 +46,9 @@ File names like `smoke.test.ts` are for human organization only. Mutating tests must follow the create → assert → delete lifecycle: 1. Generate a unique prefix with `testPrefix()` (e.g. `cli-a1b2c3d4`). -2. Create resources using that prefix in the name. -3. Assert only on resources this test created (by name or ID in list/view output). -4. Delete those resources in `finally`. +2. Create resources using that prefix in the name (via CLI under test, or via `setup.ts` when seeding fixtures). +3. **Register teardown immediately** after creation succeeds — before assertions — so resources are cleaned up even when a test fails mid-way. For resources created via CLI: `fixture.registerCleanup(() => deleteIngestToken(tenant, created.id))`. Setup helpers in `setup.ts` register their own cleanup. +4. Assert only on resources this test created (by name or ID in list/view output). **Do not:** @@ -74,12 +74,13 @@ Prefix pattern: `cli-<8 hex chars>`. A future sweeper can match `^cli-` to clean `parseJsonOutput` throws when the CLI exits non-zero, so a successful parse means the command succeeded. ```typescript -// Good — assert on a resource this test created +// Good — register cleanup right after create, before assertions const prefix = testPrefix(); const result = await fixture.runCli`observe ingest-token create --name ${prefix}-token`; -const tokens = parseJsonOutput(result) as Token[]; -expect(tokens.some((t) => t.name === `${prefix}-token`)).toBe(true); +const created = parseJsonOutput(result) as Token; +fixture.registerCleanup(() => deleteIngestToken(tenant, created.id)); +expect(created.name).toBe(`${prefix}-token`); // Good — validate response shape; datasets are guaranteed on any functional tenant expect(Array.isArray(datasets)).toBe(true); @@ -135,3 +136,9 @@ await fixture.runCli` ## Parallelism Tests are designed to run in parallel against a shared tenant. Unique prefixes and the ownership rules above make that safe. `bun run test:integration` runs with `--concurrent --max-concurrency 5`. Use `test.serial` only when a test genuinely cannot run alongside others (rare). + +## Cleanup and setup helpers + +**Cleanup** (`integration/cleanup.ts`) — every resource created during a test must be cleaned up. Register teardown with `fixture.registerCleanup()` immediately after creation, before assertions. Cleanups run in LIFO order when the fixture is torn down; failures are logged but do not fail the test. + +**Setup** (`integration/setup.ts`) — when a test needs resources in the environment that aren't part of what it's testing, use setup helpers to create them via API instead of CLI. This includes resources the CLI can't create yet, but also resources the CLI _can_ create when the test simply doesn't care about exercising that path. Setup helpers register their own cleanup automatically. diff --git a/integration/cleanup.ts b/integration/cleanup.ts new file mode 100644 index 0000000..0a7e722 --- /dev/null +++ b/integration/cleanup.ts @@ -0,0 +1,42 @@ +/** + * API teardown helpers for integration tests — not part of the CLI surface under test. + * + * Register these with `fixture.registerCleanup()`; assertions belong on CLI output only. + */ + +import { deleteDatastream as gqlDeleteDatastream } from "../src/gql/datastream/delete-datastream"; +import { deleteDataset as gqlDeleteDataset } from "../src/gql/dataset/delete-dataset"; +import { deleteIngestToken as gqlDeleteIngestToken } from "../src/gql/ingest-token/delete-ingest-token"; +import { deleteSkill as restDeleteSkill } from "../src/rest/skill/delete-skill"; +import type { Config } from "../src/lib/config"; + +export async function deleteIngestToken( + config: Config, + id: string, +): Promise { + const success = await gqlDeleteIngestToken(config, { id }); + if (!success) { + throw new Error(`deleteIngestToken returned false for ingest token ${id}`); + } +} + +export async function deleteDatastream( + config: Config, + id: string, +): Promise { + const success = await gqlDeleteDatastream(config, { id }); + if (!success) { + throw new Error(`deleteDatastream returned false for datastream ${id}`); + } +} + +export async function deleteDataset(config: Config, id: string): Promise { + const success = await gqlDeleteDataset(config, { dsid: id }); + if (!success) { + throw new Error(`deleteDataset returned false for dataset ${id}`); + } +} + +export async function deleteSkill(config: Config, id: string): Promise { + await restDeleteSkill({ config, id }); +} diff --git a/integration/dataset.test.ts b/integration/dataset.test.ts new file mode 100644 index 0000000..409879a --- /dev/null +++ b/integration/dataset.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, test } from "bun:test"; +import { + loadTenantConfig, + parseJsonOutput, + retryUntil, + testPrefix, + withIntegrationFixture, +} from "./fixture"; +import { createTestDataset } from "./setup"; + +interface DatasetListEntry { + id: string; + label: string; +} + +interface DatasetViewJson { + id: string; + label: string; + fieldList?: { name: string }[]; +} + +interface QueryRow { + test?: string | number; +} + +const tenant = loadTenantConfig(); + +describe("dataset CLI integration", () => { + test("list and view an API-created dataset", async () => { + const label = `${testPrefix()}-dataset`; + const opal = ` +filter true +make_col test:5 +`.trim(); + + await withIntegrationFixture(tenant, async (fixture) => { + // Seed a dataset via API (not under test). + const created = await createTestDataset(fixture, label, opal); + + // dataset list: exact filter finds the fixture by label. + const listFilter = `label == ${JSON.stringify(label)}`; + const listResult = await fixture.runCli` + observe dataset list \ + --format json \ + --filter ${JSON.stringify(listFilter)} + `; + const listed = parseJsonOutput(listResult) as DatasetListEntry[]; + + expect(Array.isArray(listed)).toBe(true); + expect(listed).toHaveLength(1); + expect(listed[0]?.id).toBe(created.id); + expect(listed[0]?.label).toBe(label); + + // dataset view: metadata reflects the saved OPAL pipeline. + const viewResult = await fixture.runCli` + observe dataset view ${created.id} \ + --format json + `; + const viewed = parseJsonOutput(viewResult) as DatasetViewJson; + + expect(viewed.id).toBe(created.id); + expect(viewed.label).toBe(label); + expect(viewed.fieldList?.some((field) => field.name === "test")).toBe( + true, + ); + + // query: dataset is queryable once materialized; OPAL output includes test=5. + const rows = await retryUntil( + async () => { + const queryResult = await fixture.runCli` + observe query \ + --input ${created.id} \ + --pipeline "limit 1" \ + --format json \ + --interval 30d + `; + return parseJsonOutput(queryResult) as QueryRow[]; + }, + (result) => result.length > 0, + ); + + expect(rows[0]?.test).toBe("5"); + }); + }); +}); diff --git a/integration/datastream.test.ts b/integration/datastream.test.ts new file mode 100644 index 0000000..3d7adad --- /dev/null +++ b/integration/datastream.test.ts @@ -0,0 +1,85 @@ +import { describe, expect } from "bun:test"; +import { deleteDatastream } from "./cleanup"; +import { + loadTenantConfig, + parseJsonOutput, + testCiOnly, + testPrefix, + withIntegrationFixture, +} from "./fixture"; + +interface DatastreamJson { + id: string; + name: string; + description?: string | null; + disabled?: boolean; +} + +const tenant = loadTenantConfig(); + +describe("datastream CLI integration", () => { + // Some tenants allow ingest-token writes but reject datastream create (read-only mode). + testCiOnly("create, list, view, and update", async () => { + const prefix = testPrefix(); + const name = `${prefix}-datastream`; + const description = "integration test datastream"; + + await withIntegrationFixture(tenant, async (fixture) => { + // datastream create + const createResult = await fixture.runCli` + observe datastream create \ + --name ${name} \ + --description ${JSON.stringify(description)} + `; + const created = parseJsonOutput(createResult) as DatastreamJson; + fixture.registerCleanup(() => deleteDatastream(tenant, created.id)); + + expect(typeof created.id).toBe("string"); + expect(created.id.length).toBeGreaterThan(0); + expect(created.name).toBe(name); + + // datastream list + const listResult = await fixture.runCli` + observe datastream list \ + --match ${prefix} + `; + const listed = parseJsonOutput(listResult) as DatastreamJson[]; + + expect(Array.isArray(listed)).toBe(true); + expect(listed.some((ds) => ds.id === created.id)).toBe(true); + expect(listed.some((ds) => ds.name === name)).toBe(true); + + // datastream view + const viewResult = await fixture.runCli` + observe datastream view ${created.id} + `; + const viewed = parseJsonOutput(viewResult) as DatastreamJson; + + expect(viewed.id).toBe(created.id); + expect(viewed.name).toBe(name); + expect(viewed.description).toBe(description); + + // datastream update + const updatedDescription = `${description} (updated)`; + const updateResult = await fixture.runCli` + observe datastream update ${created.id} \ + --description ${JSON.stringify(updatedDescription)} + `; + const updated = parseJsonOutput(updateResult) as DatastreamJson; + + expect(updated.id).toBe(created.id); + expect(updated.description).toBe(updatedDescription); + + // datastream view: update persisted + const viewAfterUpdateResult = await fixture.runCli` + observe datastream view ${created.id} + `; + const viewedAfterUpdate = parseJsonOutput( + viewAfterUpdateResult, + ) as DatastreamJson; + + expect(viewedAfterUpdate.id).toBe(created.id); + expect(viewedAfterUpdate.description).toBe(updatedDescription); + }); + }); +}); diff --git a/integration/fixture.test.ts b/integration/fixture.test.ts index 602693f..46d35f4 100644 --- a/integration/fixture.test.ts +++ b/integration/fixture.test.ts @@ -19,7 +19,7 @@ describe("runCli command validation", () => { expect(error).toBeInstanceOf(InvalidCliCommandError); } } finally { - fixture.cleanup(); + await fixture.cleanup(); } }); @@ -33,7 +33,7 @@ describe("runCli command validation", () => { expect(error).toBeInstanceOf(InvalidCliCommandError); } } finally { - fixture.cleanup(); + await fixture.cleanup(); } }); @@ -47,7 +47,7 @@ describe("runCli command validation", () => { expect(error).toBeInstanceOf(InvalidCliCommandError); } } finally { - fixture.cleanup(); + await fixture.cleanup(); } }); diff --git a/integration/fixture.ts b/integration/fixture.ts index 1c7b49d..a55bd79 100644 --- a/integration/fixture.ts +++ b/integration/fixture.ts @@ -61,14 +61,17 @@ export async function withIntegrationFixture( try { await fn(fixture); } finally { - fixture.cleanup(); + await fixture.cleanup(); } } +export type CleanupFn = () => void | Promise; + export class IntegrationFixture { readonly tenant: Config; readonly tempHome: string; readonly env: NodeJS.ProcessEnv; + private readonly cleanups: CleanupFn[] = []; constructor(tenant: Config) { this.tenant = tenant; @@ -106,11 +109,52 @@ export class IntegrationFixture { }; }; - cleanup(): void { + /** Register teardown to run when the fixture is cleaned up (LIFO order). */ + registerCleanup(fn: CleanupFn): void { + this.cleanups.push(fn); + } + + async cleanup(): Promise { + for (const fn of [...this.cleanups].reverse()) { + try { + await fn(); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`integration cleanup failed: ${message}`); + } + } + this.cleanups.length = 0; rmSync(this.tempHome, { recursive: true, force: true }); } } +/** + * Repeatedly run `fn` until `isReady` returns true, or throw after timeout. + * Catch and re-throw at the call site to attach test-specific context. + */ +export async function retryUntil( + fn: () => Promise, + isReady: (value: T) => boolean, + options: { + timeoutMs?: number; + intervalMs?: number; + } = {}, +): Promise { + const timeoutMs = options.timeoutMs ?? 10_000; + const intervalMs = options.intervalMs ?? 500; + const deadline = Date.now() + timeoutMs; + + while (Date.now() < deadline) { + const value = await fn(); + if (isReady(value)) { + return value; + } + await Bun.sleep(intervalMs); + } + + throw new Error(`retryUntil timed out after ${String(timeoutMs)}ms`); +} + export function parseJsonOutput(result: CliResult): unknown { if (result.exitCode !== 0) { throw new Error( diff --git a/integration/ingest-token.test.ts b/integration/ingest-token.test.ts new file mode 100644 index 0000000..12b79f1 --- /dev/null +++ b/integration/ingest-token.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, test } from "bun:test"; +import { deleteIngestToken } from "./cleanup"; +import { + loadTenantConfig, + parseJsonOutput, + testPrefix, + withIntegrationFixture, +} from "./fixture"; + +interface IngestTokenJson { + id: string; + name: string; + description?: string | null; + disabled?: boolean; + secret?: string; +} + +const tenant = loadTenantConfig(); + +describe("ingest-token CLI integration", () => { + test("create, list, view, and update", async () => { + const prefix = testPrefix(); + const name = `${prefix}-ingest-token`; + const description = "integration test token"; + + await withIntegrationFixture(tenant, async (fixture) => { + // ingest-token create + const createResult = await fixture.runCli` + observe ingest-token create \ + --name ${name} \ + --description ${JSON.stringify(description)} + `; + const created = parseJsonOutput(createResult) as IngestTokenJson; + fixture.registerCleanup(() => deleteIngestToken(tenant, created.id)); + + expect(typeof created.id).toBe("string"); + expect(created.id.length).toBeGreaterThan(0); + expect(created.name).toBe(name); + expect(created.secret).toBeDefined(); + + // ingest-token list + const listResult = await fixture.runCli` + observe ingest-token list \ + --match ${prefix} + `; + const listed = parseJsonOutput(listResult) as IngestTokenJson[]; + + expect(Array.isArray(listed)).toBe(true); + expect(listed.some((token) => token.id === created.id)).toBe(true); + expect(listed.some((token) => token.name === name)).toBe(true); + + // ingest-token view + const viewResult = await fixture.runCli` + observe ingest-token view ${created.id} + `; + const viewed = parseJsonOutput(viewResult) as IngestTokenJson; + + expect(viewed.id).toBe(created.id); + expect(viewed.name).toBe(name); + expect(viewed.description).toBe(description); + + // ingest-token update + const updatedDescription = `${description} (updated)`; + const updateResult = await fixture.runCli` + observe ingest-token update ${created.id} \ + --description ${JSON.stringify(updatedDescription)} + `; + const updated = parseJsonOutput(updateResult) as IngestTokenJson; + + expect(updated.id).toBe(created.id); + expect(updated.description).toBe(updatedDescription); + + // ingest-token view: update persisted + const viewAfterUpdateResult = await fixture.runCli` + observe ingest-token view ${created.id} + `; + const viewedAfterUpdate = parseJsonOutput( + viewAfterUpdateResult, + ) as IngestTokenJson; + + expect(viewedAfterUpdate.id).toBe(created.id); + expect(viewedAfterUpdate.description).toBe(updatedDescription); + + // ingest-token update: disable token + const disableResult = await fixture.runCli` + observe ingest-token update ${created.id} \ + --disabled + `; + const disabled = parseJsonOutput(disableResult) as IngestTokenJson; + + expect(disabled.id).toBe(created.id); + expect(disabled.disabled).toBe(true); + + // ingest-token view: disabled state persisted + const viewAfterDisableResult = await fixture.runCli` + observe ingest-token view ${created.id} + `; + const viewedAfterDisable = parseJsonOutput( + viewAfterDisableResult, + ) as IngestTokenJson; + + expect(viewedAfterDisable.disabled).toBe(true); + + // ingest-token update: re-enable token + const enableResult = await fixture.runCli` + observe ingest-token update ${created.id} \ + --no-disabled + `; + const enabled = parseJsonOutput(enableResult) as IngestTokenJson; + + expect(enabled.id).toBe(created.id); + expect(enabled.disabled).toBe(false); + + // ingest-token view: enabled state persisted + const viewAfterEnableResult = await fixture.runCli` + observe ingest-token view ${created.id} + `; + const viewedAfterEnable = parseJsonOutput( + viewAfterEnableResult, + ) as IngestTokenJson; + + expect(viewedAfterEnable.disabled).toBe(false); + }); + }); +}); diff --git a/integration/setup.ts b/integration/setup.ts new file mode 100644 index 0000000..155dd14 --- /dev/null +++ b/integration/setup.ts @@ -0,0 +1,92 @@ +/** + * API setup helpers for integration tests — not part of the CLI surface under test. + * + * Helpers register matching teardown from `cleanup.ts` via the fixture; tests + * should only call `fixture.registerCleanup()` for resources they create via CLI. + */ + +import { saveDataset as gqlSaveDataset } from "../src/gql/dataset/save-dataset"; +import { listDatasets } from "../src/rest/dataset/list-datasets"; +import { createSkill as restCreateSkill } from "../src/rest/skill/create-skill"; +import { getDefaultWorkspace } from "../src/gql/workspace/get-default-workspace"; +import { SkillVisibility } from "../src/rest/generated"; +import { deleteDataset, deleteSkill } from "./cleanup"; +import type { IntegrationFixture } from "./fixture"; + +export const TEST_DATASET_DESCRIPTION = "integration test fixture"; +export const TEST_SKILL_DESCRIPTION = "integration test fixture"; + +/** Create a derived Event dataset on top of the System dataset via saveDataset. */ +export async function createTestDataset( + fixture: IntegrationFixture, + label: string, + opal: string, +): Promise>> { + const config = fixture.tenant; + const { workspace } = await getDefaultWorkspace(config); + if (!workspace) { + throw new Error("no default workspace found for integration tenant"); + } + + const systemDataset = ( + await listDatasets({ + config, + filter: 'label == "System"', + limit: 1, + }) + ).datasets[0]; + if (!systemDataset?.id) { + throw new Error("expected System dataset to exist on integration tenant"); + } + + const dataset = await gqlSaveDataset(config, { + workspaceId: workspace.id, + dataset: { + label, + description: TEST_DATASET_DESCRIPTION, + }, + query: { + outputStage: "main", + stages: [ + { + id: "main", + input: [ + { + inputName: "in", + inputRole: "Data", + datasetId: systemDataset.id, + }, + ], + pipeline: opal, + }, + ], + }, + }); + + fixture.registerCleanup(() => deleteDataset(config, dataset.id)); + + return dataset; +} + +/** Create a skill via REST (CLI has list/view only). */ +export async function createTestSkill( + fixture: IntegrationFixture, + label: string, + content: string, +): Promise>> { + const config = fixture.tenant; + + const skill = await restCreateSkill({ + config, + skillCreateRequest: { + label, + description: TEST_SKILL_DESCRIPTION, + content, + visibility: SkillVisibility.Unlisted, + }, + }); + + fixture.registerCleanup(() => deleteSkill(config, skill.id)); + + return skill; +} diff --git a/integration/skill.test.ts b/integration/skill.test.ts new file mode 100644 index 0000000..44ceae1 --- /dev/null +++ b/integration/skill.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from "bun:test"; +import { + loadTenantConfig, + parseJsonOutput, + testPrefix, + withIntegrationFixture, +} from "./fixture"; +import { createTestSkill } from "./setup"; + +interface SkillListEntry { + id: string; + label: string; + description?: string; +} + +interface SkillViewJson { + id: string; + label: string; + description?: string; + content?: string; +} + +const tenant = loadTenantConfig(); + +describe("skill CLI integration", () => { + test("list and view an API-created skill", async () => { + const prefix = testPrefix(); + const label = `${prefix}-skill`; + const content = "# Integration test skill\n\nRun the test."; + + await withIntegrationFixture(tenant, async (fixture) => { + // Seed a skill via API (not under test). + const created = await createTestSkill(fixture, label, content); + + // skill list: client-side match filter finds the fixture. + const listResult = await fixture.runCli` + observe skill list \ + --format json \ + --match ${prefix} + `; + const listed = parseJsonOutput(listResult) as SkillListEntry[]; + + expect(Array.isArray(listed)).toBe(true); + expect(listed.some((skill) => skill.id === created.id)).toBe(true); + expect(listed.some((skill) => skill.label === label)).toBe(true); + + // skill view: metadata reflects the saved skill. + const viewResult = await fixture.runCli` + observe skill view ${created.id} \ + --format json + `; + const viewed = parseJsonOutput(viewResult) as SkillViewJson; + + expect(viewed.id).toBe(created.id); + expect(viewed.label).toBe(label); + expect(viewed.description).toBe(created.description); + + // skill view --content: body matches what was saved. + const contentResult = await fixture.runCli` + observe skill view ${created.id} \ + --content + `; + + expect(contentResult.stdout.trim()).toBe(content); + }); + }); +}); diff --git a/integration/smoke.test.ts b/integration/smoke.test.ts index d63902b..27b296e 100644 --- a/integration/smoke.test.ts +++ b/integration/smoke.test.ts @@ -79,9 +79,9 @@ describe("CLI integration smoke", () => { }); }); - // Every tenant has at least a System dataset. test("query runs against a dataset from the tenant", async () => { await withIntegrationFixture(tenant, async (fixture) => { + // Pick any dataset from the tenant (all tenants are guaranteed to have at least the System dataset). const listResult = await fixture.runCli` observe dataset list \ --format json \ @@ -95,6 +95,8 @@ describe("CLI integration smoke", () => { throw new Error("expected at least one dataset"); } const datasetId = firstDataset.id; + + // query: run a simple pipeline against that dataset. const queryResult = await fixture.runCli` observe query \ --input ${datasetId} \ diff --git a/src/gql/dataset/delete-dataset.graphql b/src/gql/dataset/delete-dataset.graphql new file mode 100644 index 0000000..c3bbc95 --- /dev/null +++ b/src/gql/dataset/delete-dataset.graphql @@ -0,0 +1,6 @@ +mutation DeleteDataset($dsid: ObjectId!) { + deleteDataset(dsid: $dsid) { + success + errorMessage + } +} diff --git a/src/gql/dataset/delete-dataset.ts b/src/gql/dataset/delete-dataset.ts new file mode 100644 index 0000000..1a3cd9a --- /dev/null +++ b/src/gql/dataset/delete-dataset.ts @@ -0,0 +1,22 @@ +import type { Config } from "../../lib/config"; +import { + DeleteDatasetDocument, + type DeleteDatasetMutationVariables, +} from "../generated/graphql"; +import { executeGraphQL } from "../gql-request"; + +export async function deleteDataset( + config: Config, + variables: DeleteDatasetMutationVariables, +): Promise { + const response = await executeGraphQL( + config, + DeleteDatasetDocument, + variables, + ); + const result = response.data.deleteDataset; + if (!result) { + throw new Error("deleteDataset returned no result"); + } + return result.success; +} diff --git a/src/gql/dataset/save-dataset.graphql b/src/gql/dataset/save-dataset.graphql new file mode 100644 index 0000000..c0ced13 --- /dev/null +++ b/src/gql/dataset/save-dataset.graphql @@ -0,0 +1,13 @@ +mutation SaveDataset( + $workspaceId: ObjectId + $dataset: DatasetInput! + $query: MultiStageQueryInput! +) { + saveDataset(workspaceId: $workspaceId, dataset: $dataset, query: $query) { + dataset { + id + name + description + } + } +} diff --git a/src/gql/dataset/save-dataset.ts b/src/gql/dataset/save-dataset.ts new file mode 100644 index 0000000..e45a44e --- /dev/null +++ b/src/gql/dataset/save-dataset.ts @@ -0,0 +1,18 @@ +import type { Config } from "../../lib/config"; +import { + SaveDatasetDocument, + type SaveDatasetMutationVariables, +} from "../generated/graphql"; +import { executeGraphQL } from "../gql-request"; + +export async function saveDataset( + config: Config, + variables: SaveDatasetMutationVariables, +) { + const response = await executeGraphQL(config, SaveDatasetDocument, variables); + const result = response.data.saveDataset; + if (!result?.dataset) { + throw new Error("saveDataset returned no dataset"); + } + return result.dataset; +} diff --git a/src/gql/datastream/delete-datastream.graphql b/src/gql/datastream/delete-datastream.graphql new file mode 100644 index 0000000..400caf0 --- /dev/null +++ b/src/gql/datastream/delete-datastream.graphql @@ -0,0 +1,6 @@ +mutation DeleteDatastream($id: ObjectId!) { + deleteDatastream(id: $id) { + success + errorMessage + } +} diff --git a/src/gql/datastream/delete-datastream.ts b/src/gql/datastream/delete-datastream.ts new file mode 100644 index 0000000..0b0dadc --- /dev/null +++ b/src/gql/datastream/delete-datastream.ts @@ -0,0 +1,18 @@ +import type { Config } from "../../lib/config"; +import { + DeleteDatastreamDocument, + type DeleteDatastreamMutationVariables, +} from "../generated/graphql"; +import { executeGraphQL } from "../gql-request"; + +export async function deleteDatastream( + config: Config, + variables: DeleteDatastreamMutationVariables, +): Promise { + const response = await executeGraphQL( + config, + DeleteDatastreamDocument, + variables, + ); + return response.data.deleteDatastream.success; +} diff --git a/src/gql/ingest-token/delete-ingest-token.graphql b/src/gql/ingest-token/delete-ingest-token.graphql new file mode 100644 index 0000000..3dd5ec2 --- /dev/null +++ b/src/gql/ingest-token/delete-ingest-token.graphql @@ -0,0 +1,6 @@ +mutation DeleteIngestToken($id: ObjectId!) { + deleteIngestToken(id: $id) { + success + errorMessage + } +} diff --git a/src/gql/ingest-token/delete-ingest-token.ts b/src/gql/ingest-token/delete-ingest-token.ts new file mode 100644 index 0000000..853fb82 --- /dev/null +++ b/src/gql/ingest-token/delete-ingest-token.ts @@ -0,0 +1,18 @@ +import type { Config } from "../../lib/config"; +import { + DeleteIngestTokenDocument, + type DeleteIngestTokenMutationVariables, +} from "../generated/graphql"; +import { executeGraphQL } from "../gql-request"; + +export async function deleteIngestToken( + config: Config, + variables: DeleteIngestTokenMutationVariables, +): Promise { + const response = await executeGraphQL( + config, + DeleteIngestTokenDocument, + variables, + ); + return response.data.deleteIngestToken.success; +} diff --git a/src/rest/skill/create-skill.ts b/src/rest/skill/create-skill.ts new file mode 100644 index 0000000..f46a308 --- /dev/null +++ b/src/rest/skill/create-skill.ts @@ -0,0 +1,14 @@ +import type { Config } from "../../lib/config"; +import type { SkillCreateRequest } from "../generated"; +import { ObserveRestSDK } from "../client"; + +export async function createSkill({ + config, + skillCreateRequest, +}: { + config: Config; + skillCreateRequest: SkillCreateRequest; +}) { + const sdk = new ObserveRestSDK(config); + return sdk.skillsApi.createSkill({ skillCreateRequest }); +} diff --git a/src/rest/skill/delete-skill.ts b/src/rest/skill/delete-skill.ts new file mode 100644 index 0000000..17c0f49 --- /dev/null +++ b/src/rest/skill/delete-skill.ts @@ -0,0 +1,13 @@ +import type { Config } from "../../lib/config"; +import { ObserveRestSDK } from "../client"; + +export async function deleteSkill({ + config, + id, +}: { + config: Config; + id: string; +}): Promise { + const sdk = new ObserveRestSDK(config); + await sdk.skillsApi.deleteSkill({ id }); +}