Skip to content

Commit

Permalink
Add support for creating isolated in-memory instances
Browse files Browse the repository at this point in the history
Update balena-auth from 5.0.0 to 5.1.0

Change-type: minor
  • Loading branch information
thgreasi committed Aug 8, 2023
1 parent dde3a13 commit 74b8d0d
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 41 deletions.
2 changes: 1 addition & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ startup and before any calls to `fromSharedOptions()` are made.
| [options.deviceUrlsBase] | <code>String</code> | <code>&#x27;balena-devices.com&#x27;</code> | the base balena device API url to use. |
| [options.requestLimit] | <code>Number</code> | | the number of requests per requestLimitInterval that the SDK should respect. |
| [options.requestLimitInterval] | <code>Number</code> | <code>60000</code> | the timespan that the requestLimit should apply to in milliseconds, defaults to 60000 (1 minute). |
| [options.dataDirectory] | <code>String</code> | <code>&#x27;$HOME/.balena&#x27;</code> | *ignored in the browser*, the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`. |
| [options.dataDirectory] | <code>String</code> \| <code>False</code> | <code>&#x27;$HOME/.balena&#x27;</code> | *ignored in the browser unless false*, the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`. Providing `false` creates an isolated in-memory instance. |
| [options.isBrowser] | <code>Boolean</code> | | the flag to tell if the module works in the browser. If not set will be computed based on the presence of the global `window` value. |
| [options.debug] | <code>Boolean</code> | | when set will print some extra debug information. |

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Where the factory method accepts the following options:
* `apiUrl`, string, *optional*, is the balena API url. Defaults to `https://api.balena-cloud.com/`,
* `builderUrl`, string, *optional* , is the balena builder url. Defaults to `https://builder.balena-cloud.com/`,
* `deviceUrlsBase`, string, *optional*, is the base balena device API url. Defaults to `balena-devices.com`,
* `dataDirectory`, string, *optional*, *ignored in the browser*, is the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`. Defaults to `$HOME/.balena`,
* `dataDirectory`, string or false, *optional*, *ignored in the browser unless false*, specifies the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`. Providing `false` creates an isolated in-memory instance. Defaults to `$HOME/.balena`,
* `isBrowser`, boolean, *optional*, is the flag to tell if the module works in the browser. If not set will be computed based on the presence of the global `window` value,
* `debug`, boolean, *optional*, when set will print some extra debug information.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"@types/json-schema": "^7.0.9",
"@types/node": "^14.0.0",
"abortcontroller-polyfill": "^1.7.1",
"balena-auth": "^5.0.0",
"balena-auth": "^5.1.0",
"balena-errors": "^4.8.0",
"balena-hup-action-utils": "~5.0.0",
"balena-register-device": "^8.0.7",
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export interface SdkOptions {
apiUrl?: string;
builderUrl?: string;
dashboardUrl?: string;
dataDirectory?: string;
dataDirectory?: string | false;
isBrowser?: boolean;
debug?: boolean;
deviceUrlsBase?: string;
Expand Down Expand Up @@ -496,7 +496,7 @@ export const getSdk = function ($opts?: SdkOptions) {
* @param {String} [options.deviceUrlsBase='balena-devices.com'] - the base balena device API url to use.
* @param {Number} [options.requestLimit] - the number of requests per requestLimitInterval that the SDK should respect.
* @param {Number} [options.requestLimitInterval = 60000] - the timespan that the requestLimit should apply to in milliseconds, defaults to 60000 (1 minute).
* @param {String} [options.dataDirectory='$HOME/.balena'] - *ignored in the browser*, the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`.
* @param {String|False} [options.dataDirectory='$HOME/.balena'] - *ignored in the browser unless false*, the directory where the user settings are stored, normally retrieved like `require('balena-settings-client').get('dataDirectory')`. Providing `false` creates an isolated in-memory instance.
* @param {Boolean} [options.isBrowser] - the flag to tell if the module works in the browser. If not set will be computed based on the presence of the global `window` value.
* @param {Boolean} [options.debug] - when set will print some extra debug information.
*
Expand Down
225 changes: 189 additions & 36 deletions tests/integration/balena.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getSdk,
sdkOpts,
givenLoggedInUser,
credentials,
givenAnApplication,
} from './setup';
import { timeSuite } from '../util';

Expand All @@ -19,39 +21,42 @@ describe('Balena SDK', function () {
const validKeys = ['auth', 'models', 'logs', 'settings', 'version'];

describe('factory function', function () {
describe('given no opts', () =>
describe('given no opts', () => {
it('should return an object with valid keys', function () {
const mockBalena = getSdk();
return expect(mockBalena).to.include.keys(validKeys);
}));
expect(mockBalena).to.include.keys(validKeys);
});
});

describe('given empty opts', () =>
describe('given empty opts', () => {
it('should return an object with valid keys', function () {
const mockBalena = getSdk({});
return expect(mockBalena).to.include.keys(validKeys);
}));
expect(mockBalena).to.include.keys(validKeys);
});
});

describe('given opts', () =>
describe('given opts', () => {
it('should return an object with valid keys', function () {
const mockBalena = getSdk(sdkOpts);
return expect(mockBalena).to.include.keys(validKeys);
}));
expect(mockBalena).to.include.keys(validKeys);
});
});

describe('version', () =>
describe('version', () => {
it('should match the package.json version', function () {
const mockBalena = getSdk();
return expect(mockBalena).to.have.property(
'version',
packageJSON.version,
);
}));
expect(mockBalena).to.have.property('version', packageJSON.version);
});
});
});

it('should expose a pinejs client instance', () =>
expect(balena.pine).to.exist);
it('should expose a pinejs client instance', () => {
expect(balena.pine).to.exist;
});

it('should expose an balena-errors instance', () =>
expect(balena.errors).to.exist);
it('should expose an balena-errors instance', () => {
expect(balena.errors).to.exist;
});

describe('interception Hooks', function () {
let originalInterceptors: typeof balena.interceptors;
Expand Down Expand Up @@ -340,36 +345,184 @@ describe('Balena SDK', function () {
return expect(root['BALENA_SDK_SHARED_OPTIONS']).to.equal(opts);
}));

describe('fromSharedOptions()', () =>
describe('fromSharedOptions()', () => {
it('should return an object with valid keys', function () {
const mockBalena = balenaSdkExports.fromSharedOptions();
return expect(mockBalena).to.include.keys(validKeys);
}));
describe('constructor options', () =>
describe('Given an apiKey', function () {
});
});

describe('constructor options', () => {
describe('When initializing an SDK instance with an `apiKey` in the options', function () {
givenLoggedInUser(before);

before(function () {
return balena.models.apiKey
.create('apiKey', 'apiKeyDescription')
.then((testApiKey) => {
this.testApiKey = testApiKey;
expect(this.testApiKey).to.be.a('string');
return balena.auth.logout();
});
before(async function () {
const testApiKey = await balena.models.apiKey.create(
'apiKey',
'apiKeyDescription',
);
this.testApiKey = testApiKey;
expect(this.testApiKey).to.be.a('string');
await balena.auth.logout();
});

it('should not be used in API requests', function () {
it('should not be used in API requests', async function () {
expect(this.testApiKey).to.be.a('string');
const testSdkOpts = Object.assign({}, sdkOpts, {
const testSdkOpts = {
...sdkOpts,
apiKey: this.testApiKey,
});
};
const testSdk = getSdk(testSdkOpts);
const promise = testSdk.models.apiKey.getAll({ $top: 1 });
return expect(promise).to.be.rejected.and.eventually.have.property(
await expect(promise).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
);
});
}));
});
});

describe('storage isolation', function () {
describe('given a logged in instance', function () {
givenLoggedInUser(before);
givenAnApplication(before);

describe('creating an SDK instance with the same options', function () {
let testSdk: balenaSdk.BalenaSDK;
before(async function () {
testSdk = getSdk(sdkOpts);
});

describe('pine queries', async () => {
it('should be able to retrieve the user (using the key from the first instance)', async function () {
const [user] = await testSdk.pine.get({
resource: 'user',
options: {
$select: 'username',
$filter: {
username: credentials.username,
},
},
});
expect(user)
.to.be.an('object')
.and.have.property('username', credentials.username);
});

it('should be able to retrieve the application created by the first instance', async function () {
const apps = await testSdk.pine.get({
resource: 'application',
options: {
$select: 'id',
$filter: {
id: this.application.id,
},
},
});
expect(apps).to.have.lengthOf(1);
});
});

describe('models.application.get', async () => {
it('should be able to retrieve the application created by the first instance', async function () {
const app = await testSdk.models.application.get(
this.application.id,
{
$select: 'id',
},
);
expect(app)
.to.be.an('object')
.and.have.property('id', this.application.id);
});
});

describe('balena.auth.isLoggedIn()', async () => {
it('should return true', async function () {
expect(await testSdk.auth.isLoggedIn()).to.equal(true);
});
});

describe('balena.auth.getToken()', async () => {
it('should return the same key as the first instance', async function () {
expect(await testSdk.auth.getToken()).to.equal(
await balena.auth.getToken(),
);
});
});
});

describe('creating an SDK instance using dataDirectory: false', function () {
let testSdk: balenaSdk.BalenaSDK;
before(async function () {
testSdk = getSdk({
...sdkOpts,
dataDirectory: false,
});
});

describe('pine queries', async () => {
it('should be unauthenticated and not be able to retrieve any user', async function () {
await expect(
testSdk.pine.get({
resource: 'user',
options: {
$select: 'username',
$filter: {
username: credentials.username,
},
},
}),
).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
);
});

it('should be unauthenticated and not be able to retrieve the application created by the first instance', async function () {
const apps = await testSdk.pine.get({
resource: 'application',
options: {
$select: 'id',
$filter: {
id: this.application.id,
},
},
});
expect(apps).to.have.lengthOf(0);
});
});

describe('models.application.get', async () => {
it('should be able to retrieve the application created by the first instance', async function () {
await expect(
testSdk.models.application.get(this.application.id, {
$select: 'id',
}),
).to.be.rejected.and.eventually.have.property(
'code',
'BalenaApplicationNotFound',
);
});
});

describe('balena.auth.isLoggedIn()', async () => {
it('should return false', async function () {
expect(await testSdk.auth.isLoggedIn()).to.equal(false);
});
});

describe('balena.auth.getToken()', async () => {
it('should return no key', async function () {
await expect(
testSdk.auth.getToken(),
).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
);
});
});
});
});
});
});

0 comments on commit 74b8d0d

Please sign in to comment.