diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 70f646d..2681761 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -28,7 +28,8 @@ jobs: test: runs-on: ubuntu-latest env: - DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }} + BASIC_API_KEY: ${{ secrets.BASIC_API_KEY }} + PLUS_API_KEY: ${{ secrets.PLUS_API_KEY }} strategy: matrix: node-version: [20.x] diff --git a/src/api/query.ts b/src/api/query.ts index d06e729..32108e3 100644 --- a/src/api/query.ts +++ b/src/api/query.ts @@ -1,8 +1,11 @@ // Assuming the existence of these imports based on your Python code import { Router } from "./router"; -import { DuneQuery, QueryParameter, CreateQueryResponse } from "../types"; +import { DuneQuery, QueryParameter, CreateQueryResponse, DuneError } from "../types"; import { CreateQueryPayload, UpdateQueryPayload } from "../types/requestPayload"; +interface EditQueryResponse { + query_id: number; +} export class QueryAPI extends Router { /** * Creates a Dune Query by ID @@ -20,13 +23,8 @@ export class QueryAPI extends Router { is_private: isPrivate, query_parameters: params ? params : [], }; - try { - const responseJson = await this._post("query/", payload); - return this.getQuery(responseJson.query_id); - } catch (err: unknown) { - throw new Error(`Fokin Broken: ${err}`); - // throw new DuneError(responseJson, "CreateQueryResponse", err); - } + const responseJson = await this._post("query/", payload); + return this.getQuery(responseJson.query_id); } /** @@ -73,4 +71,34 @@ export class QueryAPI extends Router { // throw new DuneError(responseJson, "UpdateQueryResponse", err); } } + + public async archiveQuery(queryId: number): Promise { + const response = await this._post(`/query/${queryId}/archive`); + const query = await this.getQuery(response.query_id); + return query.is_archived; + } + + public async unarchiveQuery(queryId: number): Promise { + const response = await this._post(`/query/${queryId}/unarchive`); + const query = await this.getQuery(response.query_id); + return query.is_archived; + } + + public async makePrivate(queryId: number): Promise { + const response = await this._post(`/query/${queryId}/private`); + const query = await this.getQuery(response.query_id); + if (!query.is_private) { + throw new DuneError("Query was not made private!"); + } + } + + public async makePublic(queryId: number): Promise { + const responseJson = await this._post( + `/query/${queryId}/unprivate`, + ); + const query = await this.getQuery(responseJson.query_id); + if (query.is_private) { + throw new DuneError("Query is still private."); + } + } } diff --git a/src/api/router.ts b/src/api/router.ts index 29242d2..138911b 100644 --- a/src/api/router.ts +++ b/src/api/router.ts @@ -14,7 +14,7 @@ enum RequestMethod { // This class implements all the routes defined in the Dune API Docs: https://dune.com/docs/api/ export class Router { - apiKey: string; + private apiKey: string; constructor(apiKey: string) { this.apiKey = apiKey; diff --git a/tests/e2e/client.spec.ts b/tests/e2e/client.spec.ts index de83a43..b4cec28 100644 --- a/tests/e2e/client.spec.ts +++ b/tests/e2e/client.spec.ts @@ -1,13 +1,18 @@ import { expect } from "chai"; import { DuneClient, QueryParameter } from "../../src/"; import log from "loglevel"; -import { apiKey } from "./util"; +import { BASIC_KEY } from "./util"; log.setLevel("silent", true); describe("DuneClient Extensions", () => { + let client: DuneClient; + + beforeEach(() => { + client = new DuneClient(BASIC_KEY); + }); + it("execute runQuery", async () => { - const client = new DuneClient(apiKey); // https://dune.com/queries/1215383 const results = await client.runQuery(1215383, { query_parameters: [QueryParameter.text("TextField", "Plain Text")], @@ -23,7 +28,6 @@ describe("DuneClient Extensions", () => { }); it("getsLatestResults", async () => { - const client = new DuneClient(apiKey); // https://dune.com/queries/1215383 const results = await client.getLatestResult(1215383, [ QueryParameter.text("TextField", "Plain Text"), diff --git a/tests/e2e/executionAPI.spec.ts b/tests/e2e/executionAPI.spec.ts index fa60471..53cdd10 100644 --- a/tests/e2e/executionAPI.spec.ts +++ b/tests/e2e/executionAPI.spec.ts @@ -2,15 +2,20 @@ import { expect } from "chai"; import { QueryParameter, ExecutionState, ExecutionAPI } from "../../src/"; import log from "loglevel"; import { ExecutionPerformance } from "../../src/types/requestPayload"; -import { apiKey, expectAsyncThrow } from "./util"; +import { BASIC_KEY, expectAsyncThrow } from "./util"; log.setLevel("silent", true); describe("ExecutionAPI: native routes", () => { + let client: ExecutionAPI; + + beforeEach(() => { + client = new ExecutionAPI(BASIC_KEY); + }); + // This doesn't work if run too many times at once: // https://discord.com/channels/757637422384283659/1019910980634939433/1026840715701010473 it("returns expected results on sequence execute-cancel-get_status", async () => { - const client = new ExecutionAPI(apiKey); // Long running query ID. const queryID = 1229120; // Execute and check state @@ -40,7 +45,6 @@ describe("ExecutionAPI: native routes", () => { }); it("successfully executes with query parameters", async () => { - const client = new ExecutionAPI(apiKey); const queryID = 1215383; const parameters = [ QueryParameter.text("TextField", "Plain Text"), @@ -56,7 +60,6 @@ describe("ExecutionAPI: native routes", () => { }); it("execute with Large tier performance", async () => { - const client = new ExecutionAPI(apiKey); const execution = await client.executeQuery(1215383, { performance: ExecutionPerformance.Large, }); @@ -64,7 +67,6 @@ describe("ExecutionAPI: native routes", () => { }); it("returns expected results on cancelled query exection", async () => { - const client = new ExecutionAPI(apiKey); // Execute and check state const cancelledExecutionId = "01GEHEC1W8P1V5ENF66R2WY54V"; const result = await client.getExecutionResults(cancelledExecutionId); @@ -87,25 +89,31 @@ describe("ExecutionAPI: Errors", () => { // TODO these errors can't be reached because post method is private // {"error":"unknown parameters (undefined)"} // {"error":"Invalid request body payload"} + let client: ExecutionAPI; + + beforeEach(() => { + client = new ExecutionAPI(BASIC_KEY); + }); + + beforeEach(function () { + const client = new ExecutionAPI(BASIC_KEY); + }); it("returns invalid API key", async () => { - const client = new ExecutionAPI("Bad Key"); - await expectAsyncThrow(client.executeQuery(1), "invalid API Key"); + const bad_client = new ExecutionAPI("Bad Key"); + await expectAsyncThrow(bad_client.executeQuery(1), "invalid API Key"); }); it("returns Invalid request path (queryId too large)", async () => { - const client = new ExecutionAPI(apiKey); await expectAsyncThrow( client.executeQuery(99999999999999999999999999), "Invalid request path", ); }); it("returns query not found error", async () => { - const client = new ExecutionAPI(apiKey); await expectAsyncThrow(client.executeQuery(999999999), "Query not found"); await expectAsyncThrow(client.executeQuery(0), "Query not found"); }); it("returns invalid job id", async () => { - const client = new ExecutionAPI(apiKey); await expectAsyncThrow(client.executeQuery(999999999), "Query not found"); const invalidJobID = "Wonky Job ID"; @@ -118,7 +126,6 @@ describe("ExecutionAPI: Errors", () => { await expectAsyncThrow(client.cancelExecution(invalidJobID), expectedErrorMessage); }); it("fails execute with unknown query parameter", async () => { - const client = new ExecutionAPI(apiKey); const queryID = 1215383; const invalidParameterName = "Invalid Parameter Name"; await expectAsyncThrow( @@ -129,11 +136,9 @@ describe("ExecutionAPI: Errors", () => { ); }); it("does not allow to execute private queries for other accounts.", async () => { - const client = new ExecutionAPI(apiKey); await expectAsyncThrow(client.executeQuery(1348384), "Query not found"); }); it("fails with unhandled FAILED_TYPE_UNSPECIFIED when query won't compile", async () => { - const client = new ExecutionAPI(apiKey); // Execute and check state // V1 query: 1348966 await expectAsyncThrow( diff --git a/tests/e2e/queryAPI.spec.ts b/tests/e2e/queryAPI.spec.ts index 77c60e1..639125b 100644 --- a/tests/e2e/queryAPI.spec.ts +++ b/tests/e2e/queryAPI.spec.ts @@ -1,19 +1,27 @@ import { expect } from "chai"; -import { QueryParameter, DuneError, QueryAPI } from "../../src/"; -import { apiKey } from "./util"; +import { QueryParameter, QueryAPI } from "../../src/"; +import { PLUS_KEY, BASIC_KEY, expectAsyncThrow } from "./util"; + +const PREMIUM_PLAN_MESSAGE = + "CRUD queries is an advanced feature included only in our premium subscription plans. Please upgrade your plan to use it."; describe("QueryAPI: Premium - CRUD Operations", () => { + let plusClient: QueryAPI; + + beforeEach(() => { + plusClient = new QueryAPI(PLUS_KEY); + }); + it("create, get & update", async () => { - const client = new QueryAPI(apiKey); - let newQuery = await client.createQuery( + let newQuery = await plusClient.createQuery( "Name", "select 1", [QueryParameter.text("What", "name")], true, ); - let recoveredQuery = await client.getQuery(newQuery.query_id); + let recoveredQuery = await plusClient.getQuery(newQuery.query_id); expect(newQuery.query_id).to.be.equal(recoveredQuery.query_id); - let updatedQueryId = await client.updateQuery( + let updatedQueryId = await plusClient.updateQuery( newQuery.query_id, "New Name", "select 10;", @@ -21,3 +29,18 @@ describe("QueryAPI: Premium - CRUD Operations", () => { expect(updatedQueryId).to.be.equal(recoveredQuery.query_id); }); }); + +describe("QueryAPI: Errors", () => { + let basicClient: QueryAPI; + + beforeEach(() => { + basicClient = new QueryAPI(BASIC_KEY); + }); + + it("Basic Plan Failure", async () => { + await expectAsyncThrow( + basicClient.createQuery("Query Name", "select 1"), + PREMIUM_PLAN_MESSAGE, + ); + }); +}); diff --git a/tests/e2e/util.ts b/tests/e2e/util.ts index b112db5..1d4a310 100644 --- a/tests/e2e/util.ts +++ b/tests/e2e/util.ts @@ -1,8 +1,16 @@ import { expect } from "chai"; import { DuneError } from "../../src"; -const { DUNE_API_KEY } = process.env; -export const apiKey: string = DUNE_API_KEY ? DUNE_API_KEY : "No API Key"; +const { BASIC_API_KEY, PLUS_API_KEY } = process.env; +if (BASIC_API_KEY === undefined) { + throw Error("Missing ENV var: BASIC_API_KEY"); +} +if (PLUS_API_KEY === undefined) { + throw Error("Missing ENV var: PLUS_API_KEY"); +} +export const BASIC_KEY: string = BASIC_API_KEY!; +export const PLUS_KEY: string = PLUS_API_KEY!; + export const expectAsyncThrow = async ( promise: Promise,