n8n/packages/cli/test/unit/License.test.ts
Iván Ovejero dd233bdf7d
test: Fix license tests when tenantId is not default (no-changelog) (#6977)
License tests fail if the env has a non-default `tenantId`:

<details>
<summary>Stack trace</summary>

```
nt -- license

> n8n@1.3.0 test /Users/ivov/Development/n8n/packages/cli
> pnpm test:sqlite "--" "license"


> n8n@1.3.0 test:sqlite /Users/ivov/Development/n8n/packages/cli
> N8N_LOG_LEVEL=silent DB_TYPE=sqlite jest "--" "license"

 FAIL  test/unit/License.test.ts
  License
    ✕ initializes license manager (8 ms)
    ✓ attempts to activate license with provided key
    ✓ renews license (1 ms)
    ✓ check if feature is enabled (2 ms)
    ✓ check if sharing feature is enabled (1 ms)
    ✓ check fetching entitlements
    ✓ check fetching feature values (1 ms)
    ✓ check management jwt
    ✓ getMainPlan() returns the right entitlement (3 ms)
    ✓ getMainPlan() returns undefined if there is no main plan

  ● License › initializes license manager

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    - Expected
    + Received

      Object {
        "autoRenewEnabled": true,
        "autoRenewOffset": 259200,
    -   "deviceFingerprint": Any<Function>,
    -   "loadCertStr": Any<Function>,
    -   "logger": Anything,
    +   "deviceFingerprint": [Function deviceFingerprint],
    +   "loadCertStr": [Function loadCertStr],
    +   "logger": Logger {
    +     "logger": DerivedLogger {
    +       "_events": Object {
    +         "data": [Function ondata],
    +         "end": [Function bound onceWrapper],
    +         "prefinish": [Function prefinish],
    +       },
    +       "_eventsCount": 3,
    +       "_maxListeners": undefined,
    +       "_readableState": ReadableState {
    +         "autoDestroy": false,
    +         "awaitDrain": 0,
    +         "buffer": BufferList {
    +           "head": null,
    +           "length": 0,
    +           "tail": null,
    +         },
    +         "decoder": null,
    +         "defaultEncoding": "utf8",
    +         "destroyed": false,
    +         "emitClose": true,
    +         "emittedReadable": false,
    +         "encoding": null,
    +         "endEmitted": false,
    +         "ended": false,
    +         "flowing": true,
    +         "highWaterMark": 16,
    +         "length": 0,
    +         "needReadable": true,
    +         "objectMode": true,
    +         "paused": false,
    +         "pipes": Console {
    +           "__winstonerror": [Function bound transportEvent],
    +           "__winstonwarn": [Function bound transportEvent],
    +           "_events": Object {
    +             "close": [Function bound onceWrapper],
    +             "drain": [Function pipeOnDrainFunctionResult],
    +             "error": Array [
    +               [Function onerror],
    +               [Function bound transportEvent],
    +             ],
    +             "finish": [Function bound onceWrapper],
    +             "unpipe": Array [
    +               [Function bound onceWrapper],
    +               [Function onunpipe],
    +             ],
    +             "warn": [Function bound transportEvent],
    +           },
    +           "_eventsCount": 6,
    +           "_maxListeners": 30,
    +           "_writableState": WritableState {
    +             "autoDestroy": false,
    +             "bufferProcessing": false,
    +             "bufferedRequest": null,
    +             "bufferedRequestCount": 0,
    +             "corked": 0,
    +             "corkedRequestsFree": CorkedRequest {
    +               "entry": null,
    +               "finish": [Function anonymous],
    +               "next": null,
    +             },
    +             "decodeStrings": true,
    +             "defaultEncoding": "utf8",
    +             "destroyed": false,
    +             "emitClose": true,
    +             "ended": false,
    +             "ending": false,
    +             "errorEmitted": false,
    +             "finalCalled": false,
    +             "finished": false,
    +             "highWaterMark": 16,
    +             "lastBufferedRequest": null,
    +             "length": 0,
    +             "needDrain": false,
    +             "objectMode": true,
    +             "onwrite": [Function anonymous],
    +             "pendingcb": 0,
    +             "prefinished": false,
    +             "sync": true,
    +             "writecb": null,
    +             "writelen": 0,
    +             "writing": false,
    +           },
    +           "consoleWarnLevels": Object {},
    +           "eol": "
    + ",
    +           "format": Printf {
    +             "template": [Function anonymous],
    +           },
    +           "handleExceptions": undefined,
    +           "handleRejections": undefined,
    +           "level": undefined,
    +           "levels": Object {
    +             "debug": 5,
    +             "error": 0,
    +             "http": 3,
    +             "info": 2,
    +             "silly": 6,
    +             "verbose": 4,
    +             "warn": 1,
    +           },
    +           "name": "console",
    +           "parent": [Circular],
    +           "silent": undefined,
    +           "stderrLevels": Object {},
    +           "writable": true,
    +           Symbol(kCapture): false,
    +         },
    +         "pipesCount": 1,
    +         "readableListening": false,
    +         "reading": false,
    +         "readingMore": false,
    +         "resumeScheduled": true,
    +         "sync": false,
    +       },
    +       "_transformState": Object {
    +         "afterTransform": [Function bound afterTransform],
    +         "needTransform": false,
    +         "transforming": false,
    +         "writecb": null,
    +         "writechunk": null,
    +         "writeencoding": null,
    +       },
    +       "_writableState": WritableState {
    +         "autoDestroy": false,
    +         "bufferProcessing": false,
    +         "bufferedRequest": null,
    +         "bufferedRequestCount": 0,
    +         "corked": 0,
    +         "corkedRequestsFree": CorkedRequest {
    +           "entry": null,
    +           "finish": [Function anonymous],
    +           "next": null,
    +         },
    +         "decodeStrings": true,
    +         "defaultEncoding": "utf8",
    +         "destroyed": false,
    +         "emitClose": true,
    +         "ended": false,
    +         "ending": false,
    +         "errorEmitted": false,
    +         "finalCalled": false,
    +         "finished": false,
    +         "highWaterMark": 16,
    +         "lastBufferedRequest": null,
    +         "length": 0,
    +         "needDrain": false,
    +         "objectMode": true,
    +         "onwrite": [Function anonymous],
    +         "pendingcb": 0,
    +         "prefinished": false,
    +         "sync": true,
    +         "writecb": null,
    +         "writelen": 0,
    +         "writing": false,
    +       },
    +       "allowHalfOpen": true,
    +       "defaultMeta": null,
    +       "exceptions": ExceptionHandler {
    +         "handlers": Map {},
    +         "logger": [Circular],
    +       },
    +       "exitOnError": true,
    +       "format": Format {
    +         "options": Object {},
    +       },
    +       "level": "silent",
    +       "levels": Object {
    +         "debug": 5,
    +         "error": 0,
    +         "http": 3,
    +         "info": 2,
    +         "silly": 6,
    +         "verbose": 4,
    +         "warn": 1,
    +       },
    +       "profilers": Object {},
    +       "readable": true,
    +       "rejections": RejectionHandler {
    +         "handlers": Map {},
    +         "logger": [Circular],
    +       },
    +       "silent": true,
    +       "writable": true,
    +       Symbol(kCapture): false,
    +     },
    +   },
        "productIdentifier": "n8n-1.3.0",
    -   "saveCertStr": Any<Function>,
    +   "saveCertStr": [Function saveCertStr],
        "server": "https://server.com/v1",
    -   "tenantId": 1,
    +   "tenantId": 1001,
      },

    Number of calls: 1

      29 |
      30 | 	test('initializes license manager', async () => {
    > 31 | 		expect(LicenseManager).toHaveBeenCalledWith({
         | 		                       ^
      32 | 			autoRenewEnabled: true,
      33 | 			autoRenewOffset: MOCK_RENEW_OFFSET,
      34 | 			deviceFingerprint: expect.any(Function),

      at Object.<anonymous> (test/unit/License.test.ts:31:26)


 RUNS  test/integration/license.api.test.ts

Test Suites: 1 failed, 1 of 2 total
Tests:       1 failed, 9 passed, 10 total
Snapshots:   0 total
Time:        2 s, estimated 4 s
████████████████████████████████████████^C ELIFECYCLE  Command failed.
 ELIFECYCLE  Test failed. See above for more details.
```

</details>
2023-08-21 10:29:21 +02:00

146 lines
4.2 KiB
TypeScript

import { LicenseManager } from '@n8n_io/license-sdk';
import config from '@/config';
import { License } from '@/License';
import { N8N_VERSION } from '@/constants';
jest.mock('@n8n_io/license-sdk');
const MOCK_SERVER_URL = 'https://server.com/v1';
const MOCK_RENEW_OFFSET = 259200;
const MOCK_INSTANCE_ID = 'instance-id';
const MOCK_ACTIVATION_KEY = 'activation-key';
const MOCK_FEATURE_FLAG = 'feat:sharing';
const MOCK_MAIN_PLAN_ID = '1b765dc4-d39d-4ffe-9885-c56dd67c4b26';
describe('License', () => {
beforeAll(() => {
config.set('license.serverUrl', MOCK_SERVER_URL);
config.set('license.autoRenewEnabled', true);
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
config.set('license.tenantId', 1);
});
let license: License;
beforeEach(async () => {
license = new License();
await license.init(MOCK_INSTANCE_ID);
});
test('initializes license manager', async () => {
expect(LicenseManager).toHaveBeenCalledWith({
autoRenewEnabled: true,
autoRenewOffset: MOCK_RENEW_OFFSET,
deviceFingerprint: expect.any(Function),
productIdentifier: `n8n-${N8N_VERSION}`,
logger: expect.anything(),
loadCertStr: expect.any(Function),
saveCertStr: expect.any(Function),
server: MOCK_SERVER_URL,
tenantId: 1,
});
});
test('attempts to activate license with provided key', async () => {
await license.activate(MOCK_ACTIVATION_KEY);
expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY);
});
test('renews license', async () => {
await license.renew();
expect(LicenseManager.prototype.renew).toHaveBeenCalled();
});
test('check if feature is enabled', async () => {
await license.isFeatureEnabled(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
});
test('check if sharing feature is enabled', async () => {
await license.isFeatureEnabled(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
});
test('check fetching entitlements', async () => {
await license.getCurrentEntitlements();
expect(LicenseManager.prototype.getCurrentEntitlements).toHaveBeenCalled();
});
test('check fetching feature values', async () => {
license.getFeatureValue(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.getFeatureValue).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
});
test('check management jwt', async () => {
await license.getManagementJwt();
expect(LicenseManager.prototype.getManagementJwt).toHaveBeenCalled();
});
test('getMainPlan() returns the right entitlement', async () => {
// mock entitlements response
License.prototype.getCurrentEntitlements = jest.fn().mockReturnValue([
{
id: '84a9c852-1349-478d-9ad1-b3f55510e477',
productId: '670650f2-72d8-4397-898c-c249906e2cc2',
productMetadata: {},
features: {},
featureOverrides: {},
validFrom: new Date(),
validTo: new Date(),
},
{
id: MOCK_MAIN_PLAN_ID,
productId: '670650f2-72d8-4397-898c-c249906e2cc2',
productMetadata: {
terms: {
isMainPlan: true,
},
},
features: {},
featureOverrides: {},
validFrom: new Date(),
validTo: new Date(),
},
]);
jest.fn(license.getMainPlan).mockReset();
const mainPlan = license.getMainPlan();
expect(mainPlan?.id).toBe(MOCK_MAIN_PLAN_ID);
});
test('getMainPlan() returns undefined if there is no main plan', async () => {
// mock entitlements response
License.prototype.getCurrentEntitlements = jest.fn().mockReturnValue([
{
id: '84a9c852-1349-478d-9ad1-b3f55510e477',
productId: '670650f2-72d8-4397-898c-c249906e2cc2',
productMetadata: {}, // has no `productMetadata.terms.isMainPlan`!
features: {},
featureOverrides: {},
validFrom: new Date(),
validTo: new Date(),
},
{
id: 'c1aae471-c24e-4874-ad88-b97107de486c',
productId: '670650f2-72d8-4397-898c-c249906e2cc2',
productMetadata: {}, // has no `productMetadata.terms.isMainPlan`!
features: {},
featureOverrides: {},
validFrom: new Date(),
validTo: new Date(),
},
]);
jest.fn(license.getMainPlan).mockReset();
const mainPlan = license.getMainPlan();
expect(mainPlan).toBeUndefined();
});
});