diff --git a/qa-core/src/account-api/api/AccountInternalAPIClient.ts b/qa-core/src/account-api/api/AccountInternalAPIClient.ts index 1b73f51320..e04a371c97 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/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index f0398ade95..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, 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" @@ -24,4 +29,44 @@ export default class LicenseAPI extends BaseAPI { }) }, opts) } + + // 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 62c84f3647..7a3e0598df 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -2,7 +2,9 @@ import { generator } from "../../shared" import { Hosting, CreateAccountRequest } from "@budibase/types" // TODO: Refactor me to central location -export const generateAccount = (): CreateAccountRequest => { +export const generateAccount = ( + partial: Partial +): CreateAccountRequest => { const uuid = generator.guid() const email = `${uuid}@budibase.com` @@ -17,5 +19,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..8374677bd6 --- /dev/null +++ b/qa-core/src/account-api/tests/licensing/offline.spec.ts @@ -0,0 +1,79 @@ +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 + // Ignored until we set this up + xit("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 }) + }) +}) 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..4c9d14c55e --- /dev/null +++ b/qa-core/src/internal-api/api/apis/LicenseAPI.ts @@ -0,0 +1,45 @@ +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] + } +}