From ea52013503fe67d7fb6636857724a75ccfd2608c Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 15 Jul 2023 00:12:18 +0100 Subject: [PATCH] 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] + } +}