From ea52013503fe67d7fb6636857724a75ccfd2608c Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 15 Jul 2023 00:12:18 +0100 Subject: [PATCH 1/4] Offline licensing integration tests --- .../src/account-api/api/apis/LicenseAPI.ts | 42 ++++++++++- qa-core/src/account-api/fixtures/accounts.ts | 3 +- .../tests/licensing/offline.spec.ts | 72 +++++++++++++++++++ .../internal-api/api/BudibaseInternalAPI.ts | 3 + qa-core/src/internal-api/api/apis/BaseAPI.ts | 4 +- .../src/internal-api/api/apis/LicenseAPI.ts | 34 +++++++++ 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 qa-core/src/account-api/tests/licensing/offline.spec.ts create mode 100644 qa-core/src/internal-api/api/apis/LicenseAPI.ts diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index e0601fe127..a7c1d63c4b 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -1,5 +1,5 @@ import AccountInternalAPIClient from "../AccountInternalAPIClient" -import { Account, UpdateLicenseRequest } from "@budibase/types" +import { Account, CreateOfflineLicenseRequest, GetOfflineLicenseResponse, UpdateLicenseRequest } from "@budibase/types" import { Response } from "node-fetch" export default class LicenseAPI { @@ -28,4 +28,44 @@ export default class LicenseAPI { } return [response, json] } + + // TODO: Better approach for setting tenant id header + + async createOfflineLicense( + accountId: string, + tenantId: string, + body: CreateOfflineLicenseRequest, + opts: { status?: number } = {} + ): Promise { + const [response, json] = await this.client.post( + `/api/internal/accounts/${accountId}/license/offline`, + { + body, + internal: true, + headers: { + "x-budibase-tenant-id": tenantId + } + } + ) + expect(response.status).toBe(opts.status ? opts.status : 201) + return response + } + + async getOfflineLicense( + accountId: string, + tenantId: string, + opts: { status?: number } = {} + ): Promise<[Response, GetOfflineLicenseResponse]> { + const [response, json] = await this.client.get( + `/api/internal/accounts/${accountId}/license/offline`, + { + internal: true, + headers: { + "x-budibase-tenant-id": tenantId + } + } + ) + expect(response.status).toBe(opts.status ? opts.status : 200) + return [response, json] + } } diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts index c6c6fa163f..fdb499bdb6 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -1,7 +1,7 @@ import { generator } from "../../shared" import { Hosting, CreateAccountRequest } from "@budibase/types" -export const generateAccount = (): CreateAccountRequest => { +export const generateAccount = (partial: Partial): CreateAccountRequest => { const uuid = generator.guid() const email = `${uuid}@budibase.com` @@ -16,5 +16,6 @@ export const generateAccount = (): CreateAccountRequest => { size: "10+", tenantId: tenant, tenantName: tenant, + ...partial, } } diff --git a/qa-core/src/account-api/tests/licensing/offline.spec.ts b/qa-core/src/account-api/tests/licensing/offline.spec.ts new file mode 100644 index 0000000000..f9627ed7e1 --- /dev/null +++ b/qa-core/src/account-api/tests/licensing/offline.spec.ts @@ -0,0 +1,72 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixures from "../../fixtures" +import { Hosting, Feature } from "@budibase/types" + +describe("offline", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + // TODO: Currently requires a self host install + account portal + // How do we flag this as a self host specific test? + + it("creates, activates and deletes offline license", async () => { + // installation: Delete any token + await config.internalApi.license.deleteOfflineLicenseToken() + + // installation: Assert token not found + let [getTokenRes] = await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) + + // installation: Retrieve Identifier + const [getIdentifierRes, identifier] = await config.internalApi.license.getOfflineIdentifier() + + // account-portal: Create self-host account + const createAccountRequest = fixures.accounts.generateAccount({ hosting: Hosting.SELF }) + const [createAccountRes, account] = await config.accountsApi.accounts.create(createAccountRequest) + const accountId = account.accountId! + const tenantId = account.tenantId! + + // account-portal: Enable feature on license + await config.accountsApi.licenses.updateLicense(accountId, { + overrides: { + features: [Feature.OFFLINE] + } + }) + + // account-portal: Create offline token + const expireAt = new Date() + expireAt.setDate(new Date().getDate() + 1) + await config.accountsApi.licenses.createOfflineLicense( + accountId, + tenantId, + { + expireAt: expireAt.toISOString(), + installationIdentifierBase64: identifier.identifierBase64 + }) + + // account-portal: Retrieve offline token + const [getLicenseRes, offlineLicense] = await config.accountsApi.licenses.getOfflineLicense(accountId, tenantId) + + // installation: Activate offline token + await config.internalApi.license.activateOfflineLicenseToken({ + offlineLicenseToken: offlineLicense.offlineLicenseToken + }) + + // installation: Assert token found + await config.internalApi.license.getOfflineLicenseToken() + + // TODO: Assert on license for current user + + // installation: Remove the token + await config.internalApi.license.deleteOfflineLicenseToken() + + // installation: Assert token not found + await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) + }) +}) \ No newline at end of file diff --git a/qa-core/src/internal-api/api/BudibaseInternalAPI.ts b/qa-core/src/internal-api/api/BudibaseInternalAPI.ts index 316775b1b9..9b55c22deb 100644 --- a/qa-core/src/internal-api/api/BudibaseInternalAPI.ts +++ b/qa-core/src/internal-api/api/BudibaseInternalAPI.ts @@ -11,6 +11,7 @@ import DatasourcesAPI from "./apis/DatasourcesAPI" import IntegrationsAPI from "./apis/IntegrationsAPI" import QueriesAPI from "./apis/QueriesAPI" import PermissionsAPI from "./apis/PermissionsAPI" +import LicenseAPI from "./apis/LicenseAPI" import BudibaseInternalAPIClient from "./BudibaseInternalAPIClient" import { State } from "../../types" @@ -30,6 +31,7 @@ export default class BudibaseInternalAPI { integrations: IntegrationsAPI queries: QueriesAPI permissions: PermissionsAPI + license: LicenseAPI constructor(state: State) { this.client = new BudibaseInternalAPIClient(state) @@ -47,5 +49,6 @@ export default class BudibaseInternalAPI { this.integrations = new IntegrationsAPI(this.client) this.queries = new QueriesAPI(this.client) this.permissions = new PermissionsAPI(this.client) + this.license = new LicenseAPI(this.client) } } diff --git a/qa-core/src/internal-api/api/apis/BaseAPI.ts b/qa-core/src/internal-api/api/apis/BaseAPI.ts index b7eae45087..c0a3b344d6 100644 --- a/qa-core/src/internal-api/api/apis/BaseAPI.ts +++ b/qa-core/src/internal-api/api/apis/BaseAPI.ts @@ -8,9 +8,9 @@ export default class BaseAPI { this.client = client } - async get(url: string): Promise<[Response, any]> { + async get(url: string, status?: number): Promise<[Response, any]> { const [response, json] = await this.client.get(url) - expect(response).toHaveStatusCode(200) + expect(response).toHaveStatusCode(status ? status : 200) return [response, json] } diff --git a/qa-core/src/internal-api/api/apis/LicenseAPI.ts b/qa-core/src/internal-api/api/apis/LicenseAPI.ts new file mode 100644 index 0000000000..772ead9166 --- /dev/null +++ b/qa-core/src/internal-api/api/apis/LicenseAPI.ts @@ -0,0 +1,34 @@ +import { Response } from "node-fetch" +import { + ActivateOfflineLicenseTokenRequest, + GetOfflineIdentifierResponse, + GetOfflineLicenseTokenResponse, +} from "@budibase/types" +import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient" +import BaseAPI from "./BaseAPI" + +export default class LicenseAPI extends BaseAPI { + constructor(client: BudibaseInternalAPIClient) { + super(client) + } + + async getOfflineLicenseToken(opts: { status?: number } = {}): Promise<[Response, GetOfflineLicenseTokenResponse]> { + const [response, body] = await this.get(`/global/license/offline`, opts.status) + return [response, body] + } + + async deleteOfflineLicenseToken(): Promise<[Response]> { + const [response] = await this.del(`/global/license/offline`, 204) + return [response] + } + + async activateOfflineLicenseToken(body: ActivateOfflineLicenseTokenRequest): Promise<[Response]> { + const [response] = await this.post(`/global/license/offline`, body) + return [response] + } + + async getOfflineIdentifier(): Promise<[Response, GetOfflineIdentifierResponse]> { + const [response, body] = await this.get(`/global/license/offline/identifier`) + return [response, body] + } +} From 6aac59c248e9300d5318be10516281d0efcaf304 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 25 Jul 2023 11:12:36 +0100 Subject: [PATCH 2/4] Add ignore to offline licensing test --- qa-core/src/account-api/api/AccountInternalAPIClient.ts | 1 + qa-core/src/account-api/tests/licensing/offline.spec.ts | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qa-core/src/account-api/api/AccountInternalAPIClient.ts b/qa-core/src/account-api/api/AccountInternalAPIClient.ts index 1b73f51320..7388dadf25 100644 --- a/qa-core/src/account-api/api/AccountInternalAPIClient.ts +++ b/qa-core/src/account-api/api/AccountInternalAPIClient.ts @@ -49,6 +49,7 @@ export default class AccountInternalAPIClient { requestOptions.headers = { ...requestOptions.headers, ...{ [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY }, + cookie: "" } } diff --git a/qa-core/src/account-api/tests/licensing/offline.spec.ts b/qa-core/src/account-api/tests/licensing/offline.spec.ts index f9627ed7e1..6e4c74360f 100644 --- a/qa-core/src/account-api/tests/licensing/offline.spec.ts +++ b/qa-core/src/account-api/tests/licensing/offline.spec.ts @@ -14,9 +14,8 @@ describe("offline", () => { }) // TODO: Currently requires a self host install + account portal - // How do we flag this as a self host specific test? - - it("creates, activates and deletes offline license", async () => { + // Ignored until we set this up + xit("creates, activates and deletes offline license", async () => { // installation: Delete any token await config.internalApi.license.deleteOfflineLicenseToken() From f656e97d2dfbc9611142f888b3ab0646130505e8 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 25 Jul 2023 11:28:14 +0100 Subject: [PATCH 3/4] Lint --- packages/pro | 2 +- .../api/AccountInternalAPIClient.ts | 2 +- .../src/account-api/api/apis/LicenseAPI.ts | 15 ++++++--- qa-core/src/account-api/fixtures/accounts.ts | 28 ++++++++-------- .../tests/licensing/offline.spec.ts | 32 ++++++++++++------- .../src/internal-api/api/apis/LicenseAPI.ts | 21 +++++++++--- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/packages/pro b/packages/pro index 347ee53268..b5124e76b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 347ee5326812c01ef07f0e744f691ab4823e185a +Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 diff --git a/qa-core/src/account-api/api/AccountInternalAPIClient.ts b/qa-core/src/account-api/api/AccountInternalAPIClient.ts index 7388dadf25..e04a371c97 100644 --- a/qa-core/src/account-api/api/AccountInternalAPIClient.ts +++ b/qa-core/src/account-api/api/AccountInternalAPIClient.ts @@ -49,7 +49,7 @@ export default class AccountInternalAPIClient { requestOptions.headers = { ...requestOptions.headers, ...{ [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY }, - cookie: "" + cookie: "", } } diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index a63338ee28..44579f867b 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -1,5 +1,10 @@ import AccountInternalAPIClient from "../AccountInternalAPIClient" -import { Account, CreateOfflineLicenseRequest, GetOfflineLicenseResponse, UpdateLicenseRequest } from "@budibase/types" +import { + Account, + CreateOfflineLicenseRequest, + GetOfflineLicenseResponse, + UpdateLicenseRequest, +} from "@budibase/types" import { Response } from "node-fetch" import BaseAPI from "./BaseAPI" import { APIRequestOpts } from "../../../types" @@ -39,8 +44,8 @@ export default class LicenseAPI extends BaseAPI { body, internal: true, headers: { - "x-budibase-tenant-id": tenantId - } + "x-budibase-tenant-id": tenantId, + }, } ) expect(response.status).toBe(opts.status ? opts.status : 201) @@ -57,8 +62,8 @@ export default class LicenseAPI extends BaseAPI { { internal: true, headers: { - "x-budibase-tenant-id": tenantId - } + "x-budibase-tenant-id": tenantId, + }, } ) expect(response.status).toBe(opts.status ? opts.status : 200) diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts index f3665252ec..7a3e0598df 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -2,21 +2,23 @@ import { generator } from "../../shared" import { Hosting, CreateAccountRequest } from "@budibase/types" // TODO: Refactor me to central location -export const generateAccount = (partial: Partial): CreateAccountRequest => { - const uuid = generator.guid() +export const generateAccount = ( + partial: Partial +): CreateAccountRequest => { + const uuid = generator.guid() const email = `${uuid}@budibase.com` const tenant = `tenant${uuid.replace(/-/g, "")}` - return { - email, - hosting: Hosting.CLOUD, - name: email, - password: uuid, - profession: "software_engineer", - size: "10+", - tenantId: tenant, - tenantName: tenant, - ...partial, - } + return { + email, + hosting: Hosting.CLOUD, + name: email, + password: uuid, + profession: "software_engineer", + size: "10+", + tenantId: tenant, + tenantName: tenant, + ...partial, + } } diff --git a/qa-core/src/account-api/tests/licensing/offline.spec.ts b/qa-core/src/account-api/tests/licensing/offline.spec.ts index 6e4c74360f..8374677bd6 100644 --- a/qa-core/src/account-api/tests/licensing/offline.spec.ts +++ b/qa-core/src/account-api/tests/licensing/offline.spec.ts @@ -20,22 +20,28 @@ describe("offline", () => { await config.internalApi.license.deleteOfflineLicenseToken() // installation: Assert token not found - let [getTokenRes] = await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) + let [getTokenRes] = await config.internalApi.license.getOfflineLicenseToken( + { status: 404 } + ) // installation: Retrieve Identifier - const [getIdentifierRes, identifier] = await config.internalApi.license.getOfflineIdentifier() + const [getIdentifierRes, identifier] = + await config.internalApi.license.getOfflineIdentifier() // account-portal: Create self-host account - const createAccountRequest = fixures.accounts.generateAccount({ hosting: Hosting.SELF }) - const [createAccountRes, account] = await config.accountsApi.accounts.create(createAccountRequest) + const createAccountRequest = fixures.accounts.generateAccount({ + hosting: Hosting.SELF, + }) + const [createAccountRes, account] = + await config.accountsApi.accounts.create(createAccountRequest) const accountId = account.accountId! const tenantId = account.tenantId! // account-portal: Enable feature on license await config.accountsApi.licenses.updateLicense(accountId, { overrides: { - features: [Feature.OFFLINE] - } + features: [Feature.OFFLINE], + }, }) // account-portal: Create offline token @@ -45,16 +51,18 @@ describe("offline", () => { accountId, tenantId, { - expireAt: expireAt.toISOString(), - installationIdentifierBase64: identifier.identifierBase64 - }) + expireAt: expireAt.toISOString(), + installationIdentifierBase64: identifier.identifierBase64, + } + ) // account-portal: Retrieve offline token - const [getLicenseRes, offlineLicense] = await config.accountsApi.licenses.getOfflineLicense(accountId, tenantId) + const [getLicenseRes, offlineLicense] = + await config.accountsApi.licenses.getOfflineLicense(accountId, tenantId) // installation: Activate offline token await config.internalApi.license.activateOfflineLicenseToken({ - offlineLicenseToken: offlineLicense.offlineLicenseToken + offlineLicenseToken: offlineLicense.offlineLicenseToken, }) // installation: Assert token found @@ -68,4 +76,4 @@ describe("offline", () => { // installation: Assert token not found await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) }) -}) \ No newline at end of file +}) diff --git a/qa-core/src/internal-api/api/apis/LicenseAPI.ts b/qa-core/src/internal-api/api/apis/LicenseAPI.ts index 772ead9166..4c9d14c55e 100644 --- a/qa-core/src/internal-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/internal-api/api/apis/LicenseAPI.ts @@ -12,8 +12,13 @@ export default class LicenseAPI extends BaseAPI { super(client) } - async getOfflineLicenseToken(opts: { status?: number } = {}): Promise<[Response, GetOfflineLicenseTokenResponse]> { - const [response, body] = await this.get(`/global/license/offline`, opts.status) + async getOfflineLicenseToken( + opts: { status?: number } = {} + ): Promise<[Response, GetOfflineLicenseTokenResponse]> { + const [response, body] = await this.get( + `/global/license/offline`, + opts.status + ) return [response, body] } @@ -22,13 +27,19 @@ export default class LicenseAPI extends BaseAPI { return [response] } - async activateOfflineLicenseToken(body: ActivateOfflineLicenseTokenRequest): Promise<[Response]> { + async activateOfflineLicenseToken( + body: ActivateOfflineLicenseTokenRequest + ): Promise<[Response]> { const [response] = await this.post(`/global/license/offline`, body) return [response] } - async getOfflineIdentifier(): Promise<[Response, GetOfflineIdentifierResponse]> { - const [response, body] = await this.get(`/global/license/offline/identifier`) + async getOfflineIdentifier(): Promise< + [Response, GetOfflineIdentifierResponse] + > { + const [response, body] = await this.get( + `/global/license/offline/identifier` + ) return [response, body] } } From c07fabbc1875576fa2e620e1064dedc5b1baae3a Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 25 Jul 2023 11:35:39 +0100 Subject: [PATCH 4/4] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index b5124e76b9..347ee53268 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 +Subproject commit 347ee5326812c01ef07f0e744f691ab4823e185a