-
Notifications
You must be signed in to change notification settings - Fork 1
Add integration tests for dataset, ingest-token, datastream, and skill commands #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
obs-gh-abhinavpappu
wants to merge
1
commit into
main
Choose a base branch
from
abhinav.pappu/add-basic-integration-tests-for-cli-commands
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<void> { | ||
| 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<void> { | ||
| 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<void> { | ||
| 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<void> { | ||
| await restDeleteSkill({ config, id }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious if we could just mock the list-dataset function for the ingregartion tests instead of actually creating and deleting things from the test tenant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would make it more like a unit test vs an integration test. The main idea here is to run against a real Observe backend in order to catch contract issues where there's a difference between how the CLI assumes the Observe backend works and how the backend is actually implemented, either with the API schema or the actual behavior (e.g. we expect something to be patch-updated, but backend actually replaces, etc.). This was valuable several times for terraform-provider-observe, where the backend makes a change it assumes is safe for all callers, but actually isn't for the CLI.