├── .dockerignore
├── src
├── evrythng.polyfill.js
├── symbols.js
├── util
│ ├── buildUrl.js
│ ├── parseLinkHeader.js
│ ├── getCurrentPosition.js
│ ├── mixin.js
│ ├── buildParams.js
│ └── callback.js
├── entity
│ ├── User.js
│ ├── Place.js
│ ├── Status.js
│ ├── Me.js
│ ├── Redirector.js
│ ├── ADIOrderEvent.js
│ ├── Container.js
│ ├── PurchaseOrder.js
│ ├── AccessToken.js
│ ├── AccessPolicy.js
│ ├── OperatorAccess.js
│ ├── ReactorSchedule.js
│ ├── Batch.js
│ ├── Role.js
│ ├── Project.js
│ ├── ADIOrder.js
│ ├── Task.js
│ ├── Access.js
│ ├── SecretKey.js
│ ├── ShipmentNotice.js
│ ├── Product.js
│ ├── Permission.js
│ ├── Rule.js
│ ├── Thng.js
│ ├── Account.js
│ ├── ReactorScript.js
│ ├── Domain.js
│ ├── ShortDomain.js
│ ├── File.js
│ ├── CommissionState.js
│ ├── Application.js
│ ├── Collection.js
│ ├── ReactorLog.js
│ ├── Entity.js
│ ├── ActionType.js
│ ├── Property.js
│ └── Redirection.js
├── evrythng.js
├── setup.js
├── alias.js
├── scope
│ ├── Device.js
│ ├── AccessToken.js
│ ├── Scope.js
│ ├── User.js
│ ├── Operator.js
│ └── Application.js
├── settings.js
└── entities.js
├── .prettierignore
├── .gitignore
├── .npmignore
├── examples
├── node
│ ├── index.js
│ ├── package.json
│ └── package-lock.json
├── browser-npm-globals
│ ├── package.json
│ ├── package-lock.json
│ └── index.html
└── browser-npm-browserify
│ ├── index.js
│ ├── index.html
│ └── package.json
├── test
├── e2e
│ ├── config.js
│ └── dataGenerator.js
├── helpers
│ ├── apiUrl.js
│ ├── paths.js
│ ├── responses.js
│ ├── dummy.js
│ ├── data.js
│ └── apiMock.js
├── README.md
├── integration
│ ├── entity
│ │ ├── rules.spec.js
│ │ ├── domains.spec.js
│ │ ├── shortDomains.spec.js
│ │ ├── commissionState.spec.js
│ │ ├── secretKey.spec.js
│ │ ├── adiOrderEvents.spec.js
│ │ ├── me.spec.js
│ │ ├── accountRedirector.spec.js
│ │ ├── accounts.spec.js
│ │ ├── accesses.spec.js
│ │ ├── locations.spec.js
│ │ ├── applicationRedirector.spec.js
│ │ ├── tasks.spec.js
│ │ ├── projects.spec.js
│ │ ├── roles.spec.js
│ │ ├── thngs.spec.js
│ │ ├── places.spec.js
│ │ ├── products.spec.js
│ │ ├── collections.spec.js
│ │ ├── files.spec.js
│ │ ├── applications.spec.js
│ │ ├── adiOrders.spec.js
│ │ ├── actionTypes.spec.js
│ │ ├── properties.spec.js
│ │ ├── shipmentNotice.spec.js
│ │ ├── accessTokens.spec.js
│ │ ├── actions.spec.js
│ │ ├── accessPolicies.spec.js
│ │ ├── redirection.spec.js
│ │ ├── purchaseOrders.spec.js
│ │ ├── operatorAccesses.spec.js
│ │ └── permissions.spec.js
│ ├── scope
│ │ ├── device.spec.js
│ │ └── operator.spec.js
│ └── misc
│ │ ├── stream.spec.js
│ │ ├── streamPages.spec.js
│ │ ├── pages.spec.js
│ │ ├── use.spec.js
│ │ ├── find.spec.js
│ │ ├── alias.spec.js
│ │ ├── rescope.spec.js
│ │ ├── upsert.spec.js
│ │ └── paramSetters.spec.js
├── unit
│ └── outDated
│ │ ├── settings.spec.js
│ │ ├── evrythng.spec.js
│ │ ├── util
│ │ ├── buildUrl.spec.js
│ │ ├── mixin.spec.js
│ │ ├── buildParams.spec.js
│ │ └── getCurrentPosition.spec.js
│ │ ├── setup.spec.js
│ │ ├── entity
│ │ ├── File.spec.js
│ │ ├── User.spec.js
│ │ ├── Place.spec.js
│ │ ├── Status.spec.js
│ │ ├── ReactorSchedule.spec.js
│ │ ├── Task.spec.js
│ │ ├── Batch.spec.js
│ │ ├── Role.spec.js
│ │ ├── Project.spec.js
│ │ ├── Permission.spec.js
│ │ ├── Thng.spec.js
│ │ ├── ReactorScript.spec.js
│ │ ├── Product.spec.js
│ │ ├── Collection.spec.js
│ │ ├── ReactorLog.spec.js
│ │ └── Application.spec.js
│ │ └── scope
│ │ ├── Scope.spec.js
│ │ ├── Application.spec.js
│ │ └── Operator.spec.js
└── require-main.js
├── Dockerfile
├── playground.html
├── .circleci
└── config.yml
├── jenkins
└── deploy.sh
├── webpack.config.js
└── package.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/src/evrythng.polyfill.js:
--------------------------------------------------------------------------------
1 | import 'cross-fetch/polyfill'
2 | export * from './evrythng'
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | terraform/*
2 | reports/*
3 | test-results/*
4 | mochawesome-report/*
5 | .nyc_output
6 | .vscode
7 | dist
--------------------------------------------------------------------------------
/src/symbols.js:
--------------------------------------------------------------------------------
1 | export default {
2 | init: Symbol('init'),
3 | path: Symbol('path'),
4 | resource: Symbol('resource')
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Update .npmignore whenever you update this file!
2 |
3 | npm-debug.log
4 | node_modules
5 | bower_components
6 | dist
7 | report
8 | .idea
9 | *.DS_Store
10 | .vscode
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Update .gitignore whenever you update this file!
2 |
3 | npm-debug.log
4 | node_modules
5 | bower_components
6 | examples
7 | report
8 | .idea
9 | *.html
10 | .vscode
11 |
--------------------------------------------------------------------------------
/examples/node/index.js:
--------------------------------------------------------------------------------
1 | const evrythng = require('evrythng')
2 |
3 | evrythng
4 | .api({
5 | url: '/time'
6 | })
7 | .then((res) => console.log(`Current time: ${res.timestamp}`))
8 | .catch(console.log)
9 |
--------------------------------------------------------------------------------
/test/e2e/config.js:
--------------------------------------------------------------------------------
1 | const { OPERATOR_API_KEY, OPERATOR_EMAIL, ACCOUNT_ID } = process.env
2 | const config = {
3 | OPERATOR_API_KEY,
4 | OPERATOR_EMAIL,
5 | ACCOUNT_ID
6 | }
7 |
8 | module.exports = config
9 |
--------------------------------------------------------------------------------
/examples/browser-npm-globals/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evrythng.js-browser-npm-globals",
3 | "version": "1.0.0",
4 | "license": "Apache-2.0",
5 | "dependencies": {
6 | "evrythng": "file:../.."
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evrythng.js-node",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "Apache-2.0",
6 | "dependencies": {
7 | "evrythng": "file:../.."
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/browser-npm-browserify/index.js:
--------------------------------------------------------------------------------
1 | const evrythng = require('evrythng')
2 |
3 | evrythng
4 | .api({
5 | url: '/time'
6 | })
7 | .then((res) => {
8 | const output = document.getElementById('output')
9 | output.innerHTML = res.timestamp
10 | })
11 | .catch(alert)
12 |
--------------------------------------------------------------------------------
/examples/browser-npm-browserify/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | evrythng.js NPM Browserify Example
6 |
7 |
8 | Current time:
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/helpers/apiUrl.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import settings from '../../src/settings'
3 |
4 | /**
5 | * Merge base apiUrl with given endpoint.
6 | *
7 | * @param {string} endpoint - Endpoint path
8 | * @return {string} - Full API url
9 | */
10 | export default function apiUrl (endpoint = '') {
11 | return `${settings.apiUrl}${endpoint}`
12 | }
13 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | # Presetup
2 |
3 | 1. `npm i`
4 | 2. Run `npm run build` or `npm run build-dev` in the main `evrythng.js` repository.
5 |
6 | # Unit tests
7 |
8 | Run `npm run test:unit`
9 |
10 | # Integration tests
11 |
12 | Run `npm run test`
13 |
14 | # e2e
15 |
16 | 1. Ensure you export all environment variables from `e2e/config.js` file
17 | 2. `npm run test:e2e`
18 |
--------------------------------------------------------------------------------
/examples/node/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evrythng.js-node",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "evrythng": {
8 | "version": "file:../..",
9 | "requires": {
10 | "@babel/runtime": "^7.4.3",
11 | "isomorphic-fetch": "^2.2.1",
12 | "node-fetch": "^2.3.0"
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine
2 |
3 | WORKDIR /srv
4 | COPY . /srv
5 |
6 | # Install dependencues
7 | RUN apk add --no-cache python3 py3-pip
8 | RUN pip3 install awscli --upgrade --user
9 |
10 | # Build
11 | RUN npm ci
12 | RUN npm run build
13 |
14 | # Deploy
15 | CMD ["sh", "-c", "~/.local/bin/aws s3 cp /srv/dist/evrythng.browser.js s3://$BUCKET/js/evrythng/$VERSION/evrythng-$VERSION.js --acl public-read"]
16 |
--------------------------------------------------------------------------------
/examples/browser-npm-globals/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evrythng.js-browser-npm-globals",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "evrythng": {
8 | "version": "file:../..",
9 | "requires": {
10 | "@babel/runtime": "^7.4.3",
11 | "isomorphic-fetch": "^2.2.1",
12 | "node-fetch": "^2.3.0"
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/browser-npm-browserify/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evrythng.js-browser-npm-browserify",
3 | "version": "1.0.0",
4 | "main": "dist/index.js",
5 | "license": "Apache-2.0",
6 | "dependencies": {
7 | "browserify": "^16.2.3",
8 | "evrythng": "file:../.."
9 | },
10 | "scripts": {
11 | "postinstall": "npm run build",
12 | "build": "mkdir -p dist && ./node_modules/.bin/browserify index.js -o dist/index.js"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/integration/entity/rules.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Rules', () => {
6 | let scope
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | })
11 |
12 | it('should add a rule resource', async () => {
13 | expect(scope.rule).to.be.a('function')
14 | })
15 |
16 | it('should run a given rule')
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/playground.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | evrythng.js playground
6 |
7 |
8 |
9 | evrythng.js Playground
10 |
11 | Open the console and use the 'evrythng' global variable to get started!
12 |
13 | Simple example:
14 | evrythng.api({ url: '/time' }).then(console.log)
15 |
16 |
--------------------------------------------------------------------------------
/src/util/buildUrl.js:
--------------------------------------------------------------------------------
1 | import buildParams from './buildParams'
2 |
3 | /**
4 | * Concatenate url with parameters from request options.
5 | *
6 | * @export
7 | * @param {Object} options request options including url and params
8 | * @returns {string}
9 | */
10 | export default function buildUrl (options) {
11 | let url = `${options.apiUrl}${options.url}`
12 |
13 | if (options.params) {
14 | url += `?${buildParams(options.params)}`
15 | }
16 |
17 | return url
18 | }
19 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | build:
4 | docker:
5 | - image: node:10.16-alpine
6 | steps:
7 | - checkout
8 | - run:
9 | name: 'Install dependencies'
10 | command: 'npm ci'
11 | - run:
12 | name: 'Build SDK'
13 | command: 'npm run build'
14 | - run:
15 | name: 'Audit dependencies'
16 | command: 'npm audit'
17 | - run:
18 | name: 'Run tests'
19 | command: 'npm test'
20 |
--------------------------------------------------------------------------------
/src/entity/User.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/users'
5 |
6 | /**
7 | * Represents a User entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class User extends Entity {
12 | /**
13 | * Return simple resource factory for AppUsers.
14 | *
15 | * @static
16 | * @return {{user: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | user: Resource.factoryFor(User, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/Place.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/places'
5 |
6 | /**
7 | * Represents a Place entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class Place extends Entity {
12 | /**
13 | * Return simple resource factory for Places.
14 | *
15 | * @static
16 | * @return {{place: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | place: Resource.factoryFor(Place, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/unit/outDated/settings.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import settings from '../../src/settings'
3 |
4 | const defaultSettings = {
5 | apiUrl: 'https://api.evrythng.com',
6 | apiKey: '',
7 | fullResponse: false,
8 | geolocation: true,
9 | timeout: 0,
10 | headers: {
11 | 'content-type': 'application/json'
12 | },
13 | interceptors: []
14 | }
15 |
16 | describe('settings', () => {
17 | it('should start with the default settings', () => {
18 | expect(settings).toEqual(defaultSettings)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/entity/Status.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import isString from 'lodash-es/isString'
4 |
5 | const path = '/status'
6 |
7 | export default class Status extends Entity {
8 | static resourceFactory () {
9 | return {
10 | status () {
11 | if (isString(arguments[0])) {
12 | throw new TypeError('There is no single resource for Status')
13 | }
14 |
15 | return Resource.factoryFor(Status, path).call(this)
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/entity/Me.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/me'
5 |
6 | /**
7 | * Represents a Me entity.
8 | *
9 | * @extends Entity
10 | */
11 | export default class Me extends Entity {
12 | /**
13 | * Return simple resource factory for Me.
14 | *
15 | * @static
16 | * @return {{me: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | me () {
21 | return Resource.factoryFor(Me, path).call(this)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/entity/Redirector.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/redirector'
5 |
6 | /**
7 | * Represents a Redirector entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class Redirector extends Entity {
12 | /**
13 | * Return simple resource factory for a Redirector.
14 | *
15 | * @static
16 | * @return {{redirector: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | redirector: Resource.factoryFor(Redirector, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/ADIOrderEvent.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/events'
5 |
6 | /**
7 | * Represents a ADIOrderEvent entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class ADIOrderEvent extends Entity {
12 | /**
13 | * Return simple resource factory for ADI Order events.
14 | *
15 | * @static
16 | * @return {{event: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | event: Resource.factoryFor(ADIOrderEvent, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/Container.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/containers'
5 |
6 | /**
7 | * Represents a Container entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class Container extends Entity {
12 | /**
13 | * Return simple resource factory for shipment notice containers.
14 | *
15 | * @static
16 | * @return {{container: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | container: Resource.factoryFor(Container, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/PurchaseOrder.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/purchaseOrders'
5 |
6 | /**
7 | * Represents a PurchaseOrder entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class PurchaseOrder extends Entity {
12 | /**
13 | * Return simple resource factory for ADI Orders.
14 | *
15 | * @static
16 | * @return {{purchaseOrder: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | purchaseOrder: Resource.factoryFor(PurchaseOrder, path)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/AccessToken.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/accessTokens'
5 |
6 | /**
7 | * Represents an Access Token entity.
8 | *
9 | * @extends Entity
10 | */
11 | export default class AccessToken extends Entity {
12 | /**
13 | * Return simple resource factory for Access Tokens.
14 | *
15 | * @static
16 | * @return {{accessToken: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | accessToken (id) {
21 | return Resource.factoryFor(AccessToken, path).call(this, id)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/integration/scope/device.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (url) => {
5 | describe('Device', () => {
6 | let device, api
7 |
8 | before(() => {
9 | device = getScope('device')
10 | api = mockApi(url)
11 | })
12 |
13 | it('should represent a Thng', async () => {
14 | api.get('/thngs/deviceThngId').reply(200, { id: 'deviceThngId', apiKey: 'apiKey' })
15 | const res = await device.init()
16 |
17 | expect(res).to.be.an('object')
18 | expect(res.apiKey).to.be.a('string')
19 | })
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/src/entity/AccessPolicy.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/accessPolicies'
5 |
6 | /**
7 | * Represents an Access Policy entity.
8 | *
9 | * @extends Entity
10 | */
11 | export default class AccessPolicy extends Entity {
12 | /**
13 | * Return simple resource factory for Access Policies.
14 | *
15 | * @static
16 | * @return {{accessPolicy: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | accessPolicy (id) {
21 | return Resource.factoryFor(AccessPolicy, path).call(this, id)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/entity/OperatorAccess.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/operatorAccess'
5 |
6 | /**
7 | * Represents an Operator Access entity.
8 | *
9 | * @extends Entity
10 | */
11 | export default class OperatorAccess extends Entity {
12 | /**
13 | * Return simple resource factory for Operator Accesses.
14 | *
15 | * @static
16 | * @return {{operatorAccess: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | operatorAccess (id) {
21 | return Resource.factoryFor(OperatorAccess, path).call(this, id)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/util/parseLinkHeader.js:
--------------------------------------------------------------------------------
1 | // Parse Link headers for API pagination.
2 | // https://gist.github.com/niallo/3109252
3 | export default function parseLinkHeader (header) {
4 | const links = {}
5 |
6 | if (header && header.length) {
7 | // Split parts by comma
8 | const parts = header.split(',')
9 | // Parse each part into a named link
10 | for (let i = 0; i < parts.length; i++) {
11 | const section = parts[i].split(';')
12 | const url = section[0].replace(/<(.*)>/, '$1').trim()
13 | const name = section[1].replace(/rel="(.*)"/, '$1').trim()
14 | links[name] = url
15 | }
16 | }
17 |
18 | return links
19 | }
20 |
--------------------------------------------------------------------------------
/test/unit/outDated/evrythng.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import * as evrythng from '../../src/evrythng'
3 |
4 | // Unit tested separately. Just to confirm the evrythng module is exposing the
5 | // right stuff.
6 | import settings from '../../src/settings'
7 | import setup from '../../src/setup'
8 |
9 | describe('evrythng', () => {
10 | describe('settings', () => {
11 | it('should expose settings', () => {
12 | expect(evrythng.settings).toBe(settings)
13 | })
14 | })
15 |
16 | describe('setup', () => {
17 | it('should expose settings setup', () => {
18 | expect(evrythng.setup).toBe(setup)
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/examples/browser-npm-globals/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | evrythng.js NPM Globals Example
6 |
7 |
8 | Current time:
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/src/entity/ReactorSchedule.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 | import symbols from '../symbols'
5 |
6 | const path = '/reactor/schedules'
7 |
8 | /**
9 | * Represents a ReactorSchedule entity object.
10 | *
11 | * @extends Entity
12 | */
13 | export default class ReactorSchedule extends Entity {
14 | static resourceFactory () {
15 | return {
16 | reactorSchedule (id) {
17 | const appPath = this instanceof Scope ? this[symbols.path] : ''
18 |
19 | return Resource.factoryFor(ReactorSchedule, appPath + path).call(this, id)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/integration/entity/domains.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Domains', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all domains', async () => {
14 | api.get('/accounts/accountId/domains').reply(200, [{ domain: 'wrxfq.tn.gg' }])
15 | const res = await scope.sharedAccount('accountId').domain().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(1)
19 | })
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/test/integration/entity/shortDomains.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Short Domains', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all short domains', async () => {
14 | api.get('/accounts/accountId/shortDomains').reply(200, ['tn.gg'])
15 | const res = await scope.sharedAccount('accountId').shortDomain().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(1)
19 | })
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/src/entity/Batch.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Task from './Task'
3 | import Resource from '../resource/Resource'
4 | import { mixinResources } from '../util/mixin'
5 |
6 | const path = '/batches'
7 | const BatchResources = mixinResources([Task])
8 |
9 | /**
10 | * Represents a Batch entity object.
11 | *
12 | * @extends Entity
13 | */
14 | export default class Batch extends BatchResources(Entity) {
15 | /**
16 | * Return simple resource factory for Batches.
17 | *
18 | * @static
19 | * @return {{batch: Function}}
20 | */
21 | static resourceFactory () {
22 | return {
23 | batch: Resource.factoryFor(Batch, path, BatchResources)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/entity/Role.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Permission from './Permission'
3 | import Resource from '../resource/Resource'
4 | import { mixinResources } from '../util/mixin'
5 |
6 | const path = '/roles'
7 | const RoleResources = mixinResources([Permission])
8 |
9 | /**
10 | * Represents a Role entity object.
11 | *
12 | * @extends Entity
13 | */
14 | export default class Role extends RoleResources(Entity) {
15 | /**
16 | * Return simple resource factory for Roles.
17 | *
18 | * @static
19 | * @return {{role: Function}}
20 | */
21 | static resourceFactory () {
22 | return {
23 | role: Resource.factoryFor(Role, path, RoleResources)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/unit/outDated/util/buildUrl.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import buildUrl from '../../../src/util/buildUrl'
3 | import paths from '../../helpers/paths'
4 |
5 | const apiUrl = paths.testBase
6 | const url = paths.dummy
7 |
8 | describe('buildUrl', () => {
9 | it('should join base apiUrl and path given in options', () => {
10 | expect(buildUrl({ apiUrl, url })).toEqual(`${apiUrl}${url}`)
11 | })
12 |
13 | it('should convert params object to query string', () => {
14 | const params = {
15 | foo: 'bar',
16 | baz: 1
17 | }
18 | const paramsStr = 'foo=bar&baz=1'
19 | expect(buildUrl({ apiUrl, url, params })).toEqual(`${apiUrl}${url}?${paramsStr}`)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/src/entity/Project.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Application from './Application'
3 | import Resource from '../resource/Resource'
4 | import { mixinResources } from '../util/mixin'
5 |
6 | const path = '/projects'
7 | const ProjectResources = mixinResources([Application])
8 |
9 | /**
10 | * Represents a Project entity object.
11 | *
12 | * @extends Entity
13 | */
14 | export default class Project extends ProjectResources(Entity) {
15 | /**
16 | * Return simple resource factory for Projects.
17 | *
18 | * @static
19 | * @return {{project: Function}}
20 | */
21 | static resourceFactory () {
22 | return {
23 | project: Resource.factoryFor(Project, path, ProjectResources)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/entity/ADIOrder.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import ADIOrderEvent from './ADIOrderEvent'
3 | import Resource from '../resource/Resource'
4 | import { mixinResources } from '../util/mixin'
5 |
6 | const path = '/adis/orders'
7 | const ADIOrderResources = mixinResources([ADIOrderEvent])
8 |
9 | /**
10 | * Represents a ADIOrder entity object.
11 | *
12 | * @extends Entity
13 | */
14 | export default class ADIOrder extends ADIOrderResources(Entity) {
15 | /**
16 | * Return simple resource factory for ADI Orders.
17 | *
18 | * @static
19 | * @return {{adiOrder: Function}}
20 | */
21 | static resourceFactory () {
22 | return {
23 | adiOrder: Resource.factoryFor(ADIOrder, path, ADIOrderResources)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jenkins/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Use a Docker container to build and deploy a new SDK build to the S3 bucket
4 | # used for distribution (publish to npm happens separately).
5 | #
6 | # Required environment variables:
7 | # BUCKET - The S3 bucket to push the built SDK file.
8 | # VERSION - The new SDK version being published.
9 | # AWS_ACCESS_KEY_ID - AWS Access Key ID with permission to put to the bucket.
10 | # AWS_SECRET_ACCESS_KEY - AWS Secret Key ID corresponding to the AWS_ACCESS_KEY_ID.
11 |
12 | docker build -t evrythng-js-deploy .
13 |
14 | docker run \
15 | -e "BUCKET=$BUCKET" \
16 | -e "VERSION=$VERSION" \
17 | -e "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" \
18 | -e "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" \
19 | evrythng-js-deploy
20 |
--------------------------------------------------------------------------------
/test/unit/outDated/setup.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import settings from '../../src/settings'
3 | import setup from '../../src/setup'
4 |
5 | // Copy initial settings
6 | const initialSettings = Object.assign({}, settings)
7 |
8 | describe('setup', () => {
9 | afterEach(() => setup(initialSettings))
10 |
11 | it('should merge settings with custom settings', () => {
12 | const customSettings = {
13 | apiUrl: 'https://test-api.evrythng.net',
14 | fullResponse: true
15 | }
16 | setup(customSettings)
17 |
18 | expect(settings.apiUrl).toEqual(customSettings.apiUrl)
19 | expect(settings.fullResponse).toEqual(customSettings.fullResponse)
20 | expect(settings.geolocation).toEqual(initialSettings.geolocation)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/integration/entity/commissionState.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | let scope, api
6 |
7 | describe('Commission State', () => {
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it("should read a Thng's commissioning state", async () => {
14 | api
15 | .get('/thngs/gs1%3A21%3A23984736/commissionState')
16 | .reply(200, { state: 'not_commissioned' })
17 | const res = await scope.thng('gs1:21:23984736').commissionState().read()
18 |
19 | expect(res).to.be.an('object')
20 | expect(res.state).to.equal('not_commissioned')
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/test/require-main.js:
--------------------------------------------------------------------------------
1 | const paths = {
2 | evrythng: 'evrythng.polyfill',
3 | 'isomorphic-fetch': '../node_modules/whatwg-fetch/fetch'
4 | }
5 |
6 | if (typeof module !== 'undefined') {
7 | module.exports = paths
8 | } else {
9 | const tests = []
10 | for (const file in window.__karma__.files) {
11 | if (window.__karma__.files.hasOwnProperty(file)) {
12 | if (/spec\.js$/.test(file)) {
13 | tests.push(file)
14 | }
15 | }
16 | }
17 |
18 | require.config({
19 | // Karma serves files under /base, which is the basePath from your config file
20 | baseUrl: '/base/dist',
21 |
22 | deps: tests,
23 |
24 | paths,
25 |
26 | // we have to kickoff jasmine, as it is asynchronous
27 | callback: window.__karma__.start
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/src/entity/Task.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 |
5 | const path = '/tasks'
6 |
7 | /**
8 | * Represents a Task entity.
9 | *
10 | * @extends Entity
11 | */
12 | export default class Task extends Entity {
13 | /**
14 | * Return simple resource factory for Tasks.
15 | *
16 | * @static
17 | * @return {{task: Function}}
18 | */
19 | static resourceFactory () {
20 | return {
21 | task (id) {
22 | // Only allowed on Entities and Resources.
23 | if (this instanceof Scope) {
24 | throw new Error('Task is not a top-level resource.')
25 | }
26 |
27 | return Resource.factoryFor(Task, path).call(this, id)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/integration/entity/secretKey.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Secret Key', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it("should read an application's secret key", async () => {
14 | api
15 | .get('/projects/projectId/applications/applicationId/secretKey')
16 | .reply(200, { secretApiKey: 'secretApiKey' })
17 | const res = await scope.project('projectId').application('applicationId').secretKey().read()
18 |
19 | expect(res).to.be.an('object')
20 | expect(res.secretApiKey).to.be.a('string')
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/src/entity/Access.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 |
5 | const path = '/accesses'
6 |
7 | /**
8 | * Represents an Access entity.
9 | *
10 | * @extends Entity
11 | */
12 | export default class Access extends Entity {
13 | /**
14 | * Return simple resource factory for Accesses.
15 | *
16 | * @static
17 | * @return {{access: Function}}
18 | */
19 | static resourceFactory () {
20 | return {
21 | access (id) {
22 | // Only allowed on Entities and Resources.
23 | if (this instanceof Scope) {
24 | throw new Error('Access is not a top-level resource.')
25 | }
26 |
27 | return Resource.factoryFor(Access, path).call(this, id)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/entity/SecretKey.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import isString from 'lodash-es/isString'
4 |
5 | const path = '/secretKey'
6 |
7 | /**
8 | * Represents a SecretKey entity on a Trusted Application.
9 | *
10 | * @extends Entity
11 | */
12 | export default class SecretKey extends Entity {
13 | /**
14 | * Return simple resource factory for SecretKey.
15 | *
16 | * @static
17 | * @return {{secretKey: Function}}
18 | */
19 | static resourceFactory () {
20 | return {
21 | secretKey () {
22 | if (isString(arguments[0])) {
23 | throw new TypeError('There is no single resource for SecretKey')
24 | }
25 |
26 | return Resource.factoryFor(SecretKey, path).call(this)
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/entity/ShipmentNotice.js:
--------------------------------------------------------------------------------
1 | import { mixinResources } from '../util/mixin'
2 | import Container from './Container'
3 | import Entity from './Entity'
4 | import Resource from '../resource/Resource'
5 |
6 | const path = '/shipmentNotices'
7 |
8 | const ShipmentNoticeResources = mixinResources([Container])
9 |
10 | /**
11 | * Represents a ShipmentNotice entity object.
12 | *
13 | * @extends Entity
14 | */
15 | export default class ShipmentNotice extends ShipmentNoticeResources(Entity) {
16 | /**
17 | * Return simple resource factory for shipment notices.
18 | *
19 | * @static
20 | * @return {{shipmentNotice: Function}}
21 | */
22 | static resourceFactory () {
23 | return {
24 | shipmentNotice: Resource.factoryFor(ShipmentNotice, path, ShipmentNoticeResources)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/entity/Product.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Property from './Property'
3 | import Action from './Action'
4 | import Redirection from './Redirection'
5 | import Resource from '../resource/Resource'
6 | import { mixinResources } from '../util/mixin'
7 |
8 | const path = '/products'
9 | const ProductResources = mixinResources([Property, Action, Redirection])
10 |
11 | /**
12 | * Represents a Product entity object.
13 | *
14 | * @extends Entity
15 | */
16 | export default class Product extends ProductResources(Entity) {
17 | /**
18 | * Return simple resource factory for Products.
19 | *
20 | * @static
21 | * @return {{product: Function}}
22 | */
23 | static resourceFactory () {
24 | return {
25 | product: Resource.factoryFor(Product, path, ProductResources, 'product')
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/unit/outDated/util/mixin.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import mixin from '../../../src/util/mixin'
3 |
4 | const behavior = {
5 | foo () {}
6 | }
7 | class Dummy {
8 | test () {}
9 | }
10 |
11 | let Mixin
12 |
13 | describe('mixin', () => {
14 | beforeEach(() => {
15 | // real use-case scenario
16 | Mixin = mixin(behavior)(class extends Dummy {})
17 | })
18 |
19 | it('should add behavior to target', () => {
20 | expect(Mixin.prototype.foo).toBeDefined()
21 | expect(Mixin.prototype.test).toBeDefined()
22 | })
23 |
24 | it('should not modify base class', () => {
25 | expect(Dummy.prototype.foo).not.toBeDefined()
26 | expect(Dummy.prototype.test).toBeDefined()
27 | })
28 |
29 | it('should inherit from class', () => {
30 | expect(Mixin.constructor).toBe(Dummy.constructor)
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/src/entity/Permission.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 |
5 | const path = '/permissions'
6 |
7 | /**
8 | * Represents a Permission entity.
9 | *
10 | * @extends Entity
11 | */
12 | export default class Permission extends Entity {
13 | /**
14 | * Return simple resource factory for Permissions.
15 | *
16 | * @static
17 | * @return {{permission: Function}}
18 | */
19 | static resourceFactory () {
20 | return {
21 | permission (name) {
22 | // Only allowed on Entities and Resources.
23 | if (this instanceof Scope) {
24 | throw new Error('Permission is not a top-level resource.')
25 | }
26 |
27 | return Resource.factoryFor(Permission, path).call(this, name)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/util/getCurrentPosition.js:
--------------------------------------------------------------------------------
1 | // Maximum acceptable age of cached location from the browser
2 | const maximumAge = 5 * 60 * 1000
3 |
4 | /**
5 | * Get browser's current position from Geolocation API.
6 | *
7 | * @return {Promise} - Resolves with current position or rejects with failure
8 | * explanation.
9 | */
10 | export default function getCurrentPosition () {
11 | return new Promise((resolve, reject) => {
12 | if (typeof window === 'undefined' || !window.navigator.geolocation) {
13 | throw new Error('Geolocation API not available.')
14 | }
15 |
16 | const geolocationOptions = {
17 | maximumAge,
18 | timeout: 10000,
19 | enableHighAccuracy: true
20 | }
21 |
22 | window.navigator.geolocation.getCurrentPosition(
23 | resolve,
24 | (err) => reject(err),
25 | geolocationOptions
26 | )
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/src/entity/Rule.js:
--------------------------------------------------------------------------------
1 | import isString from 'lodash-es/isString'
2 | import Entity from './Entity'
3 | import Resource from '../resource/Resource'
4 |
5 | const path = '/rules'
6 |
7 | /**
8 | * Represents a Rule entity object.
9 | *
10 | * @extends Entity
11 | */
12 | export default class Rule extends Entity {
13 | /**
14 | * Return simple resource factory for Rules.
15 | *
16 | * @static
17 | * @return {{rule: Function}}
18 | */
19 | static resourceFactory () {
20 | return {
21 | rule (ruleName) {
22 | if (!isString(ruleName)) {
23 | throw new TypeError('Rule name must be a string')
24 | }
25 |
26 | return Object.assign(Resource.factoryFor(Rule, path).call(this, ruleName), {
27 | run (...args) {
28 | return Resource.prototype.create.call(this, ...args)
29 | }
30 | })
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/entity/Thng.js:
--------------------------------------------------------------------------------
1 | import CommissionState from './CommissionState'
2 | import Entity from './Entity'
3 | import Property from './Property'
4 | import Action from './Action'
5 | import Location from './Location'
6 | import Redirection from './Redirection'
7 | import Resource from '../resource/Resource'
8 | import { mixinResources } from '../util/mixin'
9 |
10 | const path = '/thngs'
11 | const ThngResources = mixinResources([Property, Action, Location, Redirection, CommissionState])
12 |
13 | /**
14 | * Represents a Thng entity object.
15 | *
16 | * @extends Entity
17 | */
18 | export default class Thng extends ThngResources(Entity) {
19 | /**
20 | * Return simple resource factory for Thngs.
21 | *
22 | * @static
23 | * @return {{thng: Function}}
24 | */
25 | static resourceFactory () {
26 | return {
27 | thng: Resource.factoryFor(Thng, path, ThngResources, 'thng')
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/File.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import File from '../../../src/entity/File'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope } from '../../helpers/dummy'
7 | import { fileTemplate } from '../../helpers/data'
8 |
9 | let fileResource
10 |
11 | describe('File', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | const scope = Object.assign(dummyScope(), File.resourceFactory())
17 | fileResource = scope.file(fileTemplate.id)
18 | })
19 |
20 | it('should create new Role resource', () => {
21 | expect(fileResource instanceof Resource).toBe(true)
22 | expect(fileResource.type).toBe(File)
23 | expect(fileResource.path).toEqual(`${paths.files}/${fileTemplate.id}`)
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/User.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import User from '../../../src/entity/User'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope } from '../../helpers/dummy'
7 | import { userTemplate } from '../../helpers/data'
8 |
9 | let userResource
10 |
11 | describe('User', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | const scope = Object.assign(dummyScope(), User.resourceFactory())
17 | userResource = scope.user(userTemplate.id)
18 | })
19 |
20 | it('should create new Product resource', () => {
21 | expect(userResource instanceof Resource).toBe(true)
22 | expect(userResource.type).toBe(User)
23 | expect(userResource.path).toEqual(`${paths.users}/${userTemplate.id}`)
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Place.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Place from '../../../src/entity/Place'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope } from '../../helpers/dummy'
7 | import { placeTemplate } from '../../helpers/data'
8 |
9 | let placeResource
10 |
11 | describe('Place', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | const scope = Object.assign(dummyScope(), Place.resourceFactory())
17 | placeResource = scope.place(placeTemplate.id)
18 | })
19 |
20 | it('should create new Product resource', () => {
21 | expect(placeResource instanceof Resource).toBe(true)
22 | expect(placeResource.type).toBe(Place)
23 | expect(placeResource.path).toEqual(`${paths.places}/${placeTemplate.id}`)
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/src/entity/Account.js:
--------------------------------------------------------------------------------
1 | import Access from './Access'
2 | import Domain from './Domain'
3 | import OperatorAccess from './OperatorAccess'
4 | import Entity from './Entity'
5 | import ShortDomain from './ShortDomain'
6 | import Resource from '../resource/Resource'
7 | import { mixinResources } from '../util/mixin'
8 |
9 | const path = '/accounts'
10 | const AccountResources = mixinResources([
11 | Domain, // R
12 | ShortDomain, // R
13 | Access, // RU
14 | OperatorAccess // CRUD
15 | ])
16 |
17 | /**
18 | * Represents an Account entity object.
19 | *
20 | * @extends Entity
21 | */
22 | export default class Account extends AccountResources(Entity) {
23 | /**
24 | * Return simple resource factory for Accounts.
25 | *
26 | * @static
27 | * @return {{sharedAccount: Function}}
28 | */
29 | static resourceFactory () {
30 | return {
31 | sharedAccount: Resource.factoryFor(Account, path, AccountResources)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/entity/ReactorScript.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Status from './Status'
3 | import Resource from '../resource/Resource'
4 | import { mixinResources } from '../util/mixin'
5 | import isString from 'lodash-es/isString'
6 |
7 | const path = '/reactor/script'
8 | const ReactorScriptResources = mixinResources([Status])
9 |
10 | /**
11 | * Represents a ReactorScript entity object.
12 | *
13 | * @extends Entity
14 | */
15 | export default class ReactorScript extends ReactorScriptResources(Entity) {
16 | static resourceFactory () {
17 | return {
18 | reactorScript () {
19 | // Reactor scripts don't have single resource endpoint (e.g.: /scripts/:id)
20 | if (isString(arguments[0])) {
21 | throw new TypeError('There is no single resource for Reactor Scripts')
22 | }
23 |
24 | return Resource.factoryFor(ReactorScript, path, ReactorScriptResources).call(this)
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/integration/entity/adiOrderEvents.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('ADI Orders', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create an ADI Order event', async () => {
14 | const payload = {
15 | metadata: {
16 | type: 'encodings',
17 | tags: ['example']
18 | },
19 | ids: ['serial1', 'serial2'],
20 | customFields: { internalId: 'X7JF' },
21 | tags: ['X7JF']
22 | }
23 |
24 | api
25 | .post('/adis/orders/adiOrderId/events', payload)
26 | .reply(201, { id: 'UrCPgMhMMmPEY6awwEf6gKfb' })
27 | const res = await scope.adiOrder('adiOrderId').event().create(payload)
28 |
29 | expect(res).to.be.an('object')
30 | expect(res.id).to.be.a('string')
31 | })
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Status.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Status from '../../../src/entity/Status'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope } from '../../helpers/dummy'
7 |
8 | let statusResource
9 | let scope
10 |
11 | describe('Status', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | scope = Object.assign(dummyScope(), Status.resourceFactory())
17 | })
18 |
19 | it('should not allow single resource access', () => {
20 | const singleResource = () => scope.status('id')
21 | expect(singleResource).toThrow()
22 | })
23 |
24 | it('should create new Status resource', () => {
25 | statusResource = scope.status()
26 | expect(statusResource instanceof Resource).toBe(true)
27 | expect(statusResource.type).toBe(Status)
28 | expect(statusResource.path).toEqual(`${paths.status}`)
29 | })
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/src/entity/Domain.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 | import isString from 'lodash-es/isString'
5 |
6 | const path = '/domains'
7 |
8 | /**
9 | * Represents a Domain entity.
10 | *
11 | * @extends Entity
12 | */
13 | export default class Domain extends Entity {
14 | /**
15 | * Return simple resource factory for Domains.
16 | *
17 | * @static
18 | * @return {{domain: Function}}
19 | */
20 | static resourceFactory () {
21 | return {
22 | domain () {
23 | // Domains don't have single resource endpoint (e.g.: /domains/:id)
24 | if (isString(arguments[0])) {
25 | throw new TypeError('There is no single resource for Domains')
26 | }
27 |
28 | // Only allowed on Entities and Resources.
29 | if (this instanceof Scope) {
30 | throw new Error('Domain is not a top-level resource.')
31 | }
32 |
33 | return Resource.factoryFor(Domain, path).call(this)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/evrythng.js:
--------------------------------------------------------------------------------
1 | // Globals
2 | export { default as settings } from './settings'
3 | export { default as setup } from './setup'
4 | export { default as api } from './api'
5 | export { default as alias } from './alias'
6 | export { default as use } from './use'
7 |
8 | // Scopes
9 | export { default as AccessToken } from './scope/AccessToken'
10 | export { default as Operator } from './scope/Operator'
11 | export { default as ActionApp } from './scope/ActionApp'
12 | export { default as Application } from './scope/Application'
13 | export { default as TrustedApplication } from './scope/TrustedApplication'
14 | export { default as User } from './scope/User'
15 | export { default as Device } from './scope/Device'
16 |
17 | // Namespaces for: Entities and Symbols
18 | export { default as Entity } from './entities'
19 | export { default as Symbol } from './symbols'
20 |
21 | // Extensible internal modules
22 | export { default as _Resource } from './resource/Resource'
23 | export { default as _Entity } from './entity/Entity'
24 | export { default as _Scope } from './scope/Scope'
25 |
--------------------------------------------------------------------------------
/test/integration/entity/me.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, settings) => {
5 | describe('Me', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(settings.apiUrl)
11 | })
12 |
13 | if (settings.apiVersion == 2) {
14 | it('should read access', async () => {
15 | api.get('/me').reply(200, { id: 'operatorId' })
16 | const res = await scope.me().read()
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.id).to.be.a('string')
20 | })
21 | }
22 | if (settings.apiVersion == 1) {
23 | it('should NOT read access', async () => {
24 | let caughtError = false
25 | try {
26 | api.get('/me').reply(403, {})
27 | await scope.me().read()
28 | } catch (err) {
29 | caughtError = true
30 | expect(err).to.exist
31 | }
32 | expect(caughtError).to.be.equal(true)
33 | })
34 | }
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/src/entity/ShortDomain.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 | import isString from 'lodash-es/isString'
5 |
6 | const path = '/shortDomains'
7 |
8 | /**
9 | * Represents a ShortDomain entity.
10 | *
11 | * @extends Entity
12 | */
13 | export default class ShortDomain extends Entity {
14 | /**
15 | * Return simple resource factory for ShortDomains.
16 | *
17 | * @static
18 | * @return {{shortDomain: Function}}
19 | */
20 | static resourceFactory () {
21 | return {
22 | shortDomain () {
23 | // ShortDomains don't have single resource endpoint (e.g.: /shortDomains/:id)
24 | if (isString(arguments[0])) {
25 | throw new TypeError('There is no single resource for ShortDomains')
26 | }
27 |
28 | // Only allowed on Entities and Resources.
29 | if (this instanceof Scope) {
30 | throw new Error('ShortDomain is not a top-level resource.')
31 | }
32 |
33 | return Resource.factoryFor(ShortDomain, path).call(this)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/helpers/paths.js:
--------------------------------------------------------------------------------
1 | import { operatorTemplate, projectTemplate, applicationTemplate, userAccessTemplate } from './data'
2 |
3 | export default {
4 | testBase: 'https://api-test.evrythng.net',
5 | error: '/error',
6 | access: '/access',
7 | actions: '/actions',
8 | actionTypes: '/actions',
9 | applications: '/applications',
10 | application: `/projects/${projectTemplate.id}/applications/${applicationTemplate.id}`,
11 | batches: '/batches',
12 | collections: '/collections',
13 | dummy: '/path',
14 | files: '/files',
15 | locations: '/locations',
16 | operators: '/operators',
17 | operator: `/operators/${operatorTemplate.id}`,
18 | permissions: '/permissions',
19 | places: '/places',
20 | products: '/products',
21 | projects: '/projects',
22 | properties: '/properties',
23 | reactorScript: '/reactor/script',
24 | reactorSchedules: '/reactor/schedules',
25 | reactorLogs: '/reactor/logs',
26 | roles: '/roles',
27 | status: '/status',
28 | tasks: '/tasks',
29 | thngs: '/thngs',
30 | users: '/users',
31 | usersAccess: '/auth/evrythng/users',
32 | usersAccessValidate: `${userAccessTemplate.evrythngUser}/validate`
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/ReactorSchedule.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import ReactorSchedule from '../../../src/entity/ReactorSchedule'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyResource } from '../../helpers/dummy'
7 | import { reactorScheduleTemplate } from '../../helpers/data'
8 |
9 | let reactorScheduleResource
10 |
11 | describe('ReactorSchedule', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | const resource = Object.assign(dummyResource(), ReactorSchedule.resourceFactory())
17 | reactorScheduleResource = resource.reactorSchedule(reactorScheduleTemplate.id)
18 | })
19 |
20 | it('should create new ReactorSchedule resource', () => {
21 | expect(reactorScheduleResource instanceof Resource).toBe(true)
22 | expect(reactorScheduleResource.type).toBe(ReactorSchedule)
23 | expect(reactorScheduleResource.path).toEqual(
24 | `${paths.dummy}${paths.reactorSchedules}/${reactorScheduleTemplate.id}`
25 | )
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/src/entity/File.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 |
4 | const path = '/files'
5 |
6 | /**
7 | * Represents a File entity object.
8 | *
9 | * @extends Entity
10 | */
11 | export default class File extends Entity {
12 | /**
13 | * Return simple resource factory for Files.
14 | *
15 | * @static
16 | * @return {{file: Function}}
17 | */
18 | static resourceFactory () {
19 | return {
20 | file (id) {
21 | return Object.assign(Resource.factoryFor(File, path).call(this, id), {
22 | upload
23 | })
24 | }
25 | }
26 | }
27 | }
28 |
29 | /**
30 | * Upload file data, previously obtained from disk or the user.
31 | *
32 | * Note: Only text or encoded text is currently supported here.
33 | *
34 | * @param {*} data - Data as buffer or string.
35 | */
36 | async function upload (data) {
37 | const resource = await this.read()
38 | const opts = {
39 | method: 'put',
40 | headers: {
41 | 'Content-Type': resource.type,
42 | 'x-amz-acl': resource.privateAccess ? 'private' : 'public-read'
43 | },
44 | body: data
45 | }
46 |
47 | return fetch(resource.uploadUrl, opts)
48 | }
49 |
--------------------------------------------------------------------------------
/src/entity/CommissionState.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import Scope from '../scope/Scope'
4 | import symbols from '../symbols'
5 | import isString from 'lodash-es/isString'
6 |
7 | const path = '/commissionState'
8 |
9 | /**
10 | * Represents a Thng's commissioning state.
11 | *
12 | * @extends Entity
13 | */
14 | export default class CommissionState extends Entity {
15 | /**
16 | * Return resource factory for CommissionState.
17 | *
18 | * Only read() is permitted by the API.
19 | *
20 | * @static
21 | * @return {{commissionState: Function}}
22 | */
23 | static resourceFactory () {
24 | return {
25 | commissionState () {
26 | // commissionState don't have single resource endpoint (e.g.: /locations/:id)
27 | if (isString(arguments[0])) {
28 | throw new TypeError('There is no single resource for CommissionState')
29 | }
30 |
31 | // Concatenate on the end of the Thng resource location
32 | const thngPath = this instanceof Scope ? this[symbols.path] : ''
33 |
34 | return Resource.factoryFor(CommissionState, thngPath + path).call(this)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/integration/entity/accountRedirector.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Account Redirector', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read the account Redirector', async () => {
14 | api.get('/redirector').reply(200, { rules: [] })
15 | const res = await scope.redirector().read()
16 |
17 | expect(res).to.be.an('object')
18 | expect(res.rules).to.be.an('array')
19 | })
20 |
21 | it('should update the account redirector', async () => {
22 | const payload = {
23 | rules: [{ match: 'thng.name=test' }]
24 | }
25 | api.put('/redirector', payload).reply(200, payload)
26 | const res = await scope.redirector().update(payload)
27 |
28 | expect(res.rules).to.deep.equal(payload.rules)
29 | })
30 |
31 | it('should delete the account redirector', async () => {
32 | api.delete('/redirector').reply(204)
33 | const res = await scope.redirector().delete()
34 |
35 | expect(res).to.not.exist
36 | })
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/test/integration/scope/operator.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (url) => {
5 | describe('Operator', () => {
6 | let operator, api
7 |
8 | before(() => {
9 | operator = getScope('operator')
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read Operator resource', async () => {
14 | expect(operator.id).to.equal('operatorId')
15 | expect(operator.email).to.equal('test.user@evrythng.com')
16 | expect(operator.firstName).to.equal('Test')
17 | expect(operator.lastName).to.equal('User')
18 | })
19 |
20 | it('should allow self-same Operator update', async () => {
21 | api.put('/operators/operatorId').reply(200, {
22 | customFields: { foo: 'bar' }
23 | })
24 | const res = await operator.update({
25 | customFields: {
26 | foo: 'bar'
27 | }
28 | })
29 |
30 | // Response should be accurate of new state
31 | expect(res).to.be.an('object')
32 | expect(res.customFields.foo).to.equal('bar')
33 |
34 | // Operator should have updated state
35 | expect(operator.customFields.foo).to.equal('bar')
36 | })
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 |
3 | const path = resolve(__dirname, 'dist')
4 | const entry = './src/evrythng.polyfill.js'
5 | const library = 'evrythng'
6 |
7 | const babelrc = {
8 | presets: ['@babel/preset-env'],
9 | plugins: [['@babel/transform-runtime', { regenerator: true }]]
10 | }
11 |
12 | const browserConfig = {
13 | entry,
14 | output: {
15 | path,
16 | library,
17 | filename: 'evrythng.browser.js',
18 | libraryTarget: 'var'
19 | },
20 | module: {
21 | rules: [
22 | {
23 | test: /\.js$/,
24 | exclude: /node_modules/,
25 | use: {
26 | loader: 'babel-loader',
27 | options: babelrc
28 | }
29 | }
30 | ]
31 | }
32 | }
33 |
34 | const nodeConfig = {
35 | entry,
36 | target: 'node',
37 | output: {
38 | path,
39 | library,
40 | filename: 'evrythng.node.js',
41 | libraryTarget: 'umd',
42 | umdNamedDefine: true,
43 | globalObject: "typeof self !== 'undefined' ? self : this"
44 | },
45 | module: {
46 | rules: [
47 | {
48 | test: /\.js$/,
49 | exclude: /node_modules/
50 | }
51 | ]
52 | }
53 | }
54 |
55 | module.exports = [browserConfig, nodeConfig]
56 |
--------------------------------------------------------------------------------
/test/helpers/responses.js:
--------------------------------------------------------------------------------
1 | import * as data from './data'
2 |
3 | export default {
4 | ok: {
5 | status: 200,
6 | body: {}
7 | },
8 |
9 | noContent: {
10 | status: 204,
11 | body: null
12 | },
13 |
14 | error: {
15 | generic: {
16 | status: 400,
17 | body: {
18 | status: 400,
19 | errors: ['Generic error']
20 | }
21 | }
22 | },
23 |
24 | access: {
25 | operator: {
26 | body: {
27 | actor: {
28 | type: 'operator',
29 | id: data.operatorTemplate.id
30 | },
31 | apiKey: data.apiKey
32 | }
33 | },
34 | application: {
35 | body: {
36 | project: data.projectTemplate.id,
37 | actor: {
38 | type: 'app',
39 | id: data.applicationTemplate.id
40 | }
41 | }
42 | }
43 | },
44 |
45 | operator: {
46 | one: {
47 | body: data.operatorTemplate
48 | }
49 | },
50 |
51 | application: {
52 | one: {
53 | body: data.applicationTemplate
54 | }
55 | },
56 |
57 | entity: {
58 | one: {
59 | body: data.entityTemplate
60 | },
61 | multiple: {
62 | body: [data.entityTemplate, data.entityTemplate]
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Task.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Task from '../../../src/entity/Task'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 |
8 | let taskResource
9 | let resource
10 |
11 | describe('Task', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | resource = Object.assign(dummyResource(), Task.resourceFactory())
17 | })
18 |
19 | it('should not be allowed as top level resource (on Scopes)', () => {
20 | const scope = Object.assign(dummyScope(), Task.resourceFactory())
21 | const topLevelResource = () => scope.task()
22 | expect(topLevelResource).toThrow()
23 | })
24 |
25 | it('should create new Task resource', () => {
26 | taskResource = resource.task()
27 | expect(taskResource instanceof Resource).toBe(true)
28 | expect(taskResource.type).toBe(Task)
29 | })
30 |
31 | it('should add permissions path', () => {
32 | taskResource = resource.task()
33 | expect(taskResource.path).toEqual(`${paths.dummy}${paths.tasks}`)
34 | })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/src/setup.js:
--------------------------------------------------------------------------------
1 | import settings from './settings'
2 |
3 | /**
4 | * Override global settings. Ignore unknown settings.
5 | *
6 | * @param {Object} customSettings - Custom settings
7 | * @returns {Object} new
8 | */
9 |
10 | /** Allowed API versions */
11 | const VERSIONS = [1, 2]
12 | /** Allowed API regions */
13 | const REGIONS = ['us', 'eu']
14 | /** Map of API URLs by version and region */
15 | const API_MAP = {
16 | 2: {
17 | us: 'https://api.evrythng.io/v2',
18 | eu: 'https://api.eu.evrythng.io/v2'
19 | },
20 | 1: {
21 | us: 'https://api.evrythng.com',
22 | eu: 'https://api-eu.evrythng.com'
23 | }
24 | }
25 |
26 | export default function setup (newSettings = {}) {
27 | const { apiUrl, apiVersion = 2, region = 'us' } = newSettings
28 |
29 | if (!VERSIONS.includes(apiVersion)) {
30 | throw new Error(`Invalid apiVersion: ${apiVersion}. Choose from ${VERSIONS.join(', ')}`)
31 | }
32 | if (!REGIONS.includes(region)) {
33 | throw new Error(`Invalid region: ${region}. Choose from ${REGIONS.join(', ')}`)
34 | }
35 |
36 | // Set the API URL and region
37 | newSettings.apiUrl = apiUrl || API_MAP[apiVersion][region]
38 | newSettings.apiVersion = apiVersion
39 | newSettings.region = region
40 |
41 | return Object.assign(settings, newSettings)
42 | }
43 |
--------------------------------------------------------------------------------
/test/integration/misc/stream.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('stream', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should stream Thngs once at a time', (done) => {
14 | const linkUrl = encodeURIComponent(url)
15 | api.get('/thngs').reply(200, [{ name: 'Thng 1' }, { name: 'Thng 2' }], {
16 | link: `<${linkUrl}%2Fthngs%3FperPage%3D30%26sortOrder%3DDESCENDING%26nextPageToken%3DU7hXyw5DVQ8QT7fYsbyEpdAp>; rel="next"`
17 | })
18 | api
19 | .get('/thngs?perPage=30&sortOrder=DESCENDING&nextPageToken=U7hXyw5DVQ8QT7fYsbyEpdAp')
20 | .reply(200, [{ name: 'Thng 3' }, { name: 'Thng 4' }], {
21 | link: `<${linkUrl}%2Fthngs%3FperPage%3D2%26sortOrder%3DDESCENDING%26nextPageToken%3DUprntQaysgRph8aRwFTAKPtn>; rel="next"`
22 | })
23 | const cb = (item, index) => {
24 | expect(item.name).to.be.a('string')
25 | expect(index).to.be.a('number')
26 |
27 | if (index === 2) {
28 | done()
29 | return true
30 | }
31 | }
32 |
33 | scope.thng().stream(cb)
34 | })
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/src/entity/Application.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import ReactorScript from './ReactorScript'
3 | import ReactorSchedule from './ReactorSchedule'
4 | import ReactorLog from './ReactorLog'
5 | import Redirector from './Redirector'
6 | import Resource from '../resource/Resource'
7 | import SecretKey from './SecretKey'
8 | import Scope from '../scope/Scope'
9 | import { mixinResources } from '../util/mixin'
10 |
11 | const path = '/applications'
12 | const ApplicationResources = mixinResources([
13 | ReactorScript,
14 | ReactorSchedule,
15 | ReactorLog,
16 | Redirector,
17 | SecretKey
18 | ])
19 |
20 | /**
21 | * Represents an Application entity.
22 | *
23 | * @extends Entity
24 | */
25 | export default class Application extends ApplicationResources(Entity) {
26 | /**
27 | * Return simple resource factory for Applications.
28 | *
29 | * @static
30 | * @return {{application: Function}}
31 | */
32 | static resourceFactory () {
33 | return {
34 | application (id) {
35 | // Only allowed on Entities and Resources.
36 | if (this instanceof Scope) {
37 | throw new Error('Application is not a top-level resource.')
38 | }
39 |
40 | return Resource.factoryFor(Application, path, ApplicationResources).call(this, id)
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test/integration/entity/accounts.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Accounts', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all shared accounts', async () => {
14 | api.get('/accounts').reply(200, [{ id: 'accountId' }])
15 | const res = await scope.sharedAccount().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(1)
19 | })
20 |
21 | it('should read a shared account', async () => {
22 | api.get('/accounts/accountId').reply(200, { id: 'accountId' })
23 | const res = await scope.sharedAccount('accountId').read()
24 |
25 | expect(res).to.be.an('object')
26 | expect(res.id).to.be.a('string')
27 | })
28 |
29 | it('should update a shared account', async () => {
30 | const payload = { customFields: { env: 'test' } }
31 | api.put('/accounts/accountId', payload).reply(200, payload)
32 | const res = await scope.sharedAccount('accountId').update(payload)
33 |
34 | expect(res).to.be.an('object')
35 | expect(res.customFields).to.deep.equal(payload.customFields)
36 | })
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Batch.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Batch from '../../../src/entity/Batch'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 | import { batchTemplate } from '../../helpers/data'
8 |
9 | let batchResource
10 | let batch
11 |
12 | describe('Batch', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | const scope = Object.assign(dummyScope(), Batch.resourceFactory())
18 | batchResource = scope.batch(batchTemplate.id)
19 | })
20 |
21 | it('should create new Batch resource', () => {
22 | expect(batchResource instanceof Resource).toBe(true)
23 | expect(batchResource.type).toBe(Batch)
24 | expect(batchResource.path).toEqual(`${paths.batches}/${batchTemplate.id}`)
25 | })
26 |
27 | it('should have nested task resource', () => {
28 | expect(batchResource.task).toBeDefined()
29 | })
30 | })
31 |
32 | describe('access', () => {
33 | beforeEach(() => {
34 | batch = new Batch(dummyResource())
35 | })
36 |
37 | it('should have task resource', () => {
38 | expect(batch.task).toBeDefined()
39 | })
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Role.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Role from '../../../src/entity/Role'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 | import { roleTemplate } from '../../helpers/data'
8 |
9 | let roleResource
10 | let role
11 |
12 | describe('Role', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | const scope = Object.assign(dummyScope(), Role.resourceFactory())
18 | roleResource = scope.role(roleTemplate.id)
19 | })
20 |
21 | it('should create new Role resource', () => {
22 | expect(roleResource instanceof Resource).toBe(true)
23 | expect(roleResource.type).toBe(Role)
24 | expect(roleResource.path).toEqual(`${paths.roles}/${roleTemplate.id}`)
25 | })
26 |
27 | it('should have nested permission resource', () => {
28 | expect(roleResource.permission).toBeDefined()
29 | })
30 | })
31 |
32 | describe('access', () => {
33 | beforeEach(() => {
34 | role = new Role(dummyResource())
35 | })
36 |
37 | it('should have permission resource', () => {
38 | expect(role.permission).toBeDefined()
39 | })
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/test/integration/entity/accesses.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Accesses', () => {
6 | let scope, api
7 | before(() => {
8 | scope = getScope(scopeType)
9 | api = mockApi(url)
10 | })
11 |
12 | it('should read all account accesses', async () => {
13 | api.get('/accounts/accountId/accesses').reply(200, [{ id: 'accessId' }])
14 | const res = await scope.sharedAccount('accountId').access().read()
15 |
16 | expect(res).to.have.length.gte(1)
17 | })
18 |
19 | it('should read a single account access', async () => {
20 | api.get('/accounts/accountId/accesses/accessId').reply(200, { id: 'accessId' })
21 | const res = await scope.sharedAccount('accountId').access('accessId').read()
22 |
23 | expect(res).to.be.an('object')
24 | expect(res.id).to.be.a('string')
25 | })
26 |
27 | it('should update a single account access', async () => {
28 | const payload = { role: 'admin' }
29 | api.put('/accounts/accountId/accesses/accessId', payload).reply(200, { id: 'accessId' })
30 | const res = await scope.sharedAccount('accountId').access('accessId').update(payload)
31 |
32 | expect(res).to.be.an('object')
33 | expect(res.id).to.equal('accessId')
34 | })
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/test/integration/misc/streamPages.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('streamPages', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should stream pages of Thngs', (done) => {
14 | const linkUrl = encodeURIComponent(url)
15 | api.get('/thngs').reply(200, [{ name: 'Thng 1' }, { name: 'Thng 2' }], {
16 | link: `<${linkUrl}%2Fthngs%3FperPage%3D30%26sortOrder%3DDESCENDING%26nextPageToken%3DU7hXyw5DVQ8QT7fYsbyEpdAp>; rel="next"`
17 | })
18 | api
19 | .get('/thngs?perPage=30&sortOrder=DESCENDING&nextPageToken=U7hXyw5DVQ8QT7fYsbyEpdAp')
20 | .reply(200, [{ name: 'Thng 3' }, { name: 'Thng 4' }], {
21 | link: `<${linkUrl}%2Fthngs%3FperPage%3D2%26sortOrder%3DDESCENDING%26nextPageToken%3DUprntQaysgRph8aRwFTAKPtn>; rel="next"`
22 | })
23 | const eachPageCb = (page, totalSoFar) => {
24 | expect(page.length).to.equal(2)
25 |
26 | const [item] = page
27 | expect(item.name).to.be.a('string')
28 |
29 | if (totalSoFar === 2) {
30 | done()
31 | return true
32 | }
33 | }
34 |
35 | scope.thng().streamPages(eachPageCb)
36 | })
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/test/helpers/dummy.js:
--------------------------------------------------------------------------------
1 | import Scope from '../../src/scope/Scope'
2 | import Resource from '../../src/resource/Resource'
3 | import Entity from '../../src/entity/Entity'
4 | import paths from './paths'
5 | import { apiKey, entityTemplate } from './data'
6 |
7 | // Any call to dummy creators below should be inside test blocks (either
8 | // `beforeAll`, `beforeEach` or `it`).
9 |
10 | /**
11 | * Return new simplistic Scope as base for scope mixins.
12 | *
13 | * @param {string} [key] - Optional API Key
14 | * @return {Scope}
15 | */
16 | export function dummyScope (key = apiKey) {
17 | return new Scope(key)
18 | }
19 |
20 | /**
21 | * Return new simplistic Resource as base for resource mixins.
22 | *
23 | * @param {Entity} [EntityType] - Entity class
24 | * @param {string} [path] - Path for resource
25 | * @return {Resource}
26 | */
27 | export function dummyResource (EntityType = Entity, path = paths.dummy) {
28 | const scope = dummyScope()
29 | return new Resource(scope, path, EntityType)
30 | }
31 |
32 | /**
33 | * Return new simplistic Entity as base for entity mixins.
34 | *
35 | * @param {Entity} [EntityType] - Entity class
36 | * @param {Object} [body] - Predefined entity body
37 | * @return {Entity}
38 | */
39 | export function dummyEntity (EntityType = Entity, body = entityTemplate) {
40 | const resource = dummyResource()
41 | return new EntityType(resource, body)
42 | }
43 |
--------------------------------------------------------------------------------
/test/integration/entity/locations.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | let scope, api
6 |
7 | describe('Locations', () => {
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it("should update a Thng's location", async () => {
14 | const payload = [
15 | {
16 | position: { type: 'Point', coordinates: [-17.3, 36] }
17 | }
18 | ]
19 | api.put('/thngs/thngId/location', payload).reply(200, payload)
20 | const res = await scope.thng('thngId').locations().update(payload)
21 |
22 | expect(res).to.be.an('array')
23 | expect(res).to.have.length.gte(1)
24 | })
25 |
26 | it("should read a Thng's location", async () => {
27 | api.get('/thngs/thngId/location').reply(200, [
28 | {
29 | position: { type: 'Point', coordinates: [-17.3, 36] }
30 | }
31 | ])
32 | const res = await scope.thng('thngId').locations().read()
33 |
34 | expect(res).to.be.an('array')
35 | expect(res).to.have.length.gte(1)
36 | })
37 |
38 | it("should delete a Thng's location", async () => {
39 | api.delete('/thngs/thngId/location').reply(200)
40 | await scope.thng('thngId').locations().delete()
41 | })
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Project.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Project from '../../../src/entity/Project'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 | import { projectTemplate } from '../../helpers/data'
8 |
9 | let projectResource
10 | let project
11 |
12 | describe('Project', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | const scope = Object.assign(dummyScope(), Project.resourceFactory())
18 | projectResource = scope.project(projectTemplate.id)
19 | })
20 |
21 | it('should create new Product resource', () => {
22 | expect(projectResource instanceof Resource).toBe(true)
23 | expect(projectResource.type).toBe(Project)
24 | expect(projectResource.path).toEqual(`${paths.projects}/${projectTemplate.id}`)
25 | })
26 |
27 | it('should have nested application resource', () => {
28 | expect(projectResource.application).toBeDefined()
29 | })
30 | })
31 |
32 | describe('access', () => {
33 | beforeEach(() => {
34 | project = new Project(dummyResource())
35 | })
36 |
37 | it('should have application resource', () => {
38 | expect(project.application).toBeDefined()
39 | })
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/test/unit/outDated/util/buildParams.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import buildParams from '../../../src/util/buildParams'
3 |
4 | describe('buildParams', () => {
5 | it('should handle empty params', () => {
6 | expect(buildParams()).toEqual('')
7 | expect(buildParams({})).toEqual('')
8 | })
9 |
10 | it('should do nothing on string values', () => {
11 | const qs = 'foo=bar'
12 | expect(buildParams(qs)).toEqual(qs)
13 | })
14 |
15 | it('should join params', () => {
16 | const params = {
17 | foo: 'bar',
18 | baz: 1
19 | }
20 | const paramsStr = 'foo=bar&baz=1'
21 | expect(buildParams(params)).toEqual(paramsStr)
22 | })
23 |
24 | it('should encode query string', () => {
25 | const params = {
26 | 'a+b': 'a=b'
27 | }
28 | const paramsStr = 'a%2Bb=a%3Db'
29 | expect(buildParams(params)).toEqual(paramsStr)
30 | })
31 |
32 | it('should handle nested params', () => {
33 | const params = {
34 | a: {
35 | b: 'c',
36 | d: 'e=1'
37 | },
38 | f: 1
39 | }
40 | const paramsStr = 'a=b%3Dc%26d%3De%253D1&f=1'
41 | expect(buildParams(params)).toEqual(paramsStr)
42 | })
43 |
44 | it('should escape special characters', () => {
45 | const params = {
46 | a: 'va|ue'
47 | }
48 | const paramsStr = 'a=va%7Cue'
49 | expect(buildParams(params)).toEqual(paramsStr)
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/integration/misc/pages.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('pages', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read thngs through an async iterator', async () => {
14 | const params = { perPage: 2 }
15 | const linkUrl = encodeURIComponent(url)
16 | const it = scope.thng().pages({ params })
17 | api.get('/thngs?perPage=2').reply(200, [{ name: 'Thng 1' }, { name: 'Thng 2' }], {
18 | link: `<${linkUrl}%2Fthngs%3FperPage%3D2%26sortOrder%3DDESCENDING%26nextPageToken%3DU7hXyw5DVQ8QT7fYsbyEpdAp>; rel="next"`
19 | })
20 | let page = await it.next()
21 |
22 | expect(page.value.length).to.equal(2)
23 | expect(page.done).to.equal(false)
24 |
25 | api
26 | .get('/thngs?perPage=2&sortOrder=DESCENDING&nextPageToken=U7hXyw5DVQ8QT7fYsbyEpdAp')
27 | .reply(200, [{ name: 'Thng 3' }, { name: 'Thng 4' }], {
28 | link: `<${linkUrl}; rel="next"`
29 | })
30 | page = await it.next()
31 | expect(page.value.length).to.equal(2)
32 | expect(page.done).to.equal(false)
33 | })
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/test/integration/misc/use.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 | const evrythng = require('../../../')
4 |
5 | const TestPlugin = {
6 | install: (api) => {
7 | // Extend the Operator scope API
8 | api.scopes.Operator.prototype.getFoo = () => 'foo'
9 |
10 | // Add functionality to a Thng
11 | api.entities.Thng.prototype.getSummary = function () {
12 | return `${this.name} (${this.id})`
13 | }
14 | }
15 | }
16 |
17 | module.exports = (scopeType, url) => {
18 | describe('use', () => {
19 | let scope, api, thng
20 |
21 | before(async () => {
22 | scope = getScope(scopeType)
23 | api = mockApi(url)
24 |
25 | const payload = { name: 'test' }
26 | api.post('/thngs', payload).reply(201, payload)
27 | thng = await scope.thng().create(payload)
28 | })
29 |
30 | it('should not throw when installing a plugin', async () => {
31 | evrythng.use(TestPlugin)
32 | })
33 |
34 | it('should extend a scope with a new method', async () => {
35 | expect(evrythng.Operator.prototype.getFoo).to.be.a('function')
36 | expect(scope.getFoo).to.be.a('function')
37 | expect(scope.getFoo()).to.equal('foo')
38 | })
39 |
40 | it('should expect an entity with a new method', async () => {
41 | const expected = `${thng.name} (${thng.id})`
42 | expect(thng.getSummary).to.be.a('function')
43 | expect(thng.getSummary()).to.equal(expected)
44 | })
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/src/util/mixin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Return a subclass factory function that mixins the resource factory
3 | * functions from all the entities provided.
4 | * This provides a way to extend a Scope class and add a mixin that contains
5 | * all the resource methods available for that scope.
6 | *
7 | * E.g.
8 | * ```
9 | * class Operator extends OperatorAccess(Scope) {}
10 | * ```
11 | *
12 | * See: http://raganwald.com/2015/12/28/mixins-subclass-factories-and-method-advice.html#fn:simplified
13 | *
14 | * @param {Array} entities - List of entities to add resources to
15 | * @return {function(Scope)}
16 | */
17 | export function mixinResources (entities) {
18 | const resourceFactories = entities.map((e) => e.resourceFactory())
19 | const accessResources = Object.assign({}, ...resourceFactories)
20 | return (Superclass) => mixin(accessResources)(class extends Superclass {})
21 | }
22 |
23 | /**
24 | * Simplified mixin definition. Enough for our use case.
25 | * See: http://raganwald.com/2015/06/17/functional-mixins.html
26 | *
27 | * @param {Object} behaviour - Shared behaviour object literal
28 | * @param {Boolean} proto - Indicates if mixin should be applied to prototype
29 | * @return {function(target)}
30 | */
31 | export default function mixin (behaviour, proto = true) {
32 | return (target) => {
33 | for (const property of Reflect.ownKeys(behaviour)) {
34 | Object.defineProperty(proto ? target.prototype : target, property, {
35 | value: behaviour[property]
36 | })
37 | }
38 | return target
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Permission.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Permission from '../../../src/entity/Permission'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 |
8 | let permissionResource
9 | let resource
10 |
11 | describe('Permission', () => {
12 | mockApi()
13 |
14 | describe('resourceFactory', () => {
15 | beforeEach(() => {
16 | resource = Object.assign(dummyResource(), Permission.resourceFactory())
17 | })
18 |
19 | it('should not allow single resource access', () => {
20 | const singleResource = () => resource.permission('id')
21 | expect(singleResource).toThrow()
22 | })
23 |
24 | it('should not be allowed as top level resource (on Scopes)', () => {
25 | const scope = Object.assign(dummyScope(), Permission.resourceFactory())
26 | const topLevelResource = () => scope.permission()
27 | expect(topLevelResource).toThrow()
28 | })
29 |
30 | it('should create new Permission resource', () => {
31 | permissionResource = resource.permission()
32 | expect(permissionResource instanceof Resource).toBe(true)
33 | expect(permissionResource.type).toBe(Permission)
34 | })
35 |
36 | it('should add permissions path', () => {
37 | permissionResource = resource.permission()
38 | expect(permissionResource.path).toEqual(`${paths.dummy}${paths.permissions}`)
39 | })
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Thng.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Thng from '../../../src/entity/Thng'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 | import { thngTemplate } from '../../helpers/data'
8 |
9 | let thngResource
10 | let thng
11 |
12 | describe('Thng', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | const scope = Object.assign(dummyScope(), Thng.resourceFactory())
18 | thngResource = scope.thng(thngTemplate.id)
19 | })
20 |
21 | it('should create new Thng resource', () => {
22 | expect(thngResource instanceof Resource).toBe(true)
23 | expect(thngResource.type).toBe(Thng)
24 | expect(thngResource.path).toEqual(`${paths.thngs}/${thngTemplate.id}`)
25 | })
26 |
27 | it('should have nested property resource', () => {
28 | expect(thngResource.property).toBeDefined()
29 | })
30 |
31 | it('should have nested action resource', () => {
32 | expect(thngResource.action).toBeDefined()
33 | })
34 | })
35 |
36 | describe('access', () => {
37 | beforeEach(() => {
38 | thng = new Thng(dummyResource())
39 | })
40 |
41 | it('should have property resource', () => {
42 | expect(thng.property).toBeDefined()
43 | })
44 |
45 | it('should have action resource', () => {
46 | expect(thng.action).toBeDefined()
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/ReactorScript.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import ReactorScript from '../../../src/entity/ReactorScript'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 |
8 | let reactorScriptResource
9 | let reactorScript
10 | let scope
11 |
12 | describe('ReactorScript', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | scope = Object.assign(dummyScope(), ReactorScript.resourceFactory())
18 | })
19 |
20 | it('should not allow single resource access', () => {
21 | const singleResource = () => scope.reactorScript('id')
22 | expect(singleResource).toThrow()
23 | })
24 |
25 | it('should create new ReactorScript resource', () => {
26 | reactorScriptResource = scope.reactorScript()
27 | expect(reactorScriptResource instanceof Resource).toBe(true)
28 | expect(reactorScriptResource.type).toBe(ReactorScript)
29 | expect(reactorScriptResource.path).toEqual(`${paths.reactorScript}`)
30 | })
31 |
32 | it('should have nested status resource', () => {
33 | expect(reactorScriptResource.status).toBeDefined()
34 | })
35 | })
36 |
37 | describe('access', () => {
38 | beforeEach(() => {
39 | reactorScript = new ReactorScript(dummyResource())
40 | })
41 |
42 | it('should have status resource', () => {
43 | expect(reactorScript.status).toBeDefined()
44 | })
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/test/integration/misc/find.spec.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 | const chaiAsPromised = require('chai-as-promised')
4 |
5 | const { expect } = chai
6 | chai.use(chaiAsPromised)
7 |
8 | const payload = { name: 'Test Thng', identifiers: { serial: '78fd6hsd' } }
9 |
10 | module.exports = (scopeType, url) => {
11 | describe('find', () => {
12 | let operator, api
13 |
14 | before(async () => {
15 | operator = getScope(scopeType)
16 | api = mockApi(url)
17 | })
18 |
19 | it('should find Thngs by identifiers', async () => {
20 | const payload1 = { name: 'Test Thng', identifiers: { serial: '78fd6hsd' } }
21 | api.get('/thngs?filter=identifiers.serial=78fd6hsd').reply(200, [payload1])
22 |
23 | const res = await operator.thng().find(payload1.identifiers)
24 |
25 | expect(res).to.be.an('array')
26 | expect(res).to.have.length.gte(1)
27 | })
28 |
29 | it('should refuse to find if given more than one key-value', async () => {
30 | payload.identifiers.foo = 'bar'
31 |
32 | api.get('/thngs?filter=identifiers.serial%3D78fd6hsd').reply(200, payload)
33 | const attempt = operator.thng().find(payload.identifiers)
34 | return expect(attempt).to.eventually.be.rejected
35 | })
36 |
37 | it('should find Thngs by name', async () => {
38 | api.get('/thngs?filter=name%3DTest%20Thng').reply(200, [payload])
39 | const res = await operator.thng().find(payload.name)
40 |
41 | expect(res).to.be.an('array')
42 | expect(res).to.have.length.gte(1)
43 | })
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/src/alias.js:
--------------------------------------------------------------------------------
1 | import Operator from './scope/Operator'
2 | import Application from './scope/Application'
3 | import TrustedApplication from './scope/TrustedApplication'
4 | import User from './scope/User'
5 |
6 | const SCOPES = {
7 | Operator,
8 | Application,
9 | TrustedApplication,
10 | User
11 | }
12 |
13 | /**
14 | * Define a new resource type as a simple alias of an existing resource type,
15 | * and can be used to create use-case or client-specific libraries.
16 | *
17 | * The new resource is made available on the same scopes that had access before.
18 | * The existing resource is not removed or hidden from scopes. The alias in in
19 | * name only.
20 | *
21 | * For example, if a Product is to be used as a SKU by Application Users:
22 | * evrythng.alias({ product: 'sku' }, 'User')
23 | * This enables the use of a 'sku' resource in the exact same way as a product:
24 | * user.sku().read()
25 | *
26 | * @param {object} map - Object mapping the old type to a new type.
27 | * @param {string} target - Name of the scope to apply the new resource to.
28 | */
29 | export default function alias (map, target) {
30 | const scopes = Object.keys(SCOPES)
31 | if (!scopes.includes(target)) {
32 | throw new Error(`Invalid target. Choose from ${scopes.join(', ')}`)
33 | }
34 |
35 | const original = SCOPES[target]
36 | for (const existing in map) {
37 | if (!original.prototype[existing]) {
38 | throw new Error(`${existing} does not exist for ${target}`)
39 | }
40 |
41 | Object.assign(original.prototype, {
42 | [map[existing]]: original.prototype[existing]
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/unit/outDated/util/getCurrentPosition.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import getCurrentPosition from '../../../src/util/getCurrentPosition'
3 | import { positionTemplate } from '../../helpers/data'
4 |
5 | if (typeof window !== 'undefined') {
6 | describe('getCurrentPosition', () => {
7 | describe('success', () => {
8 | beforeEach(() => {
9 | spyOn(window.navigator.geolocation, 'getCurrentPosition').and.callFake((success) =>
10 | success(positionTemplate)
11 | )
12 | })
13 |
14 | it('should use defaults', () => {
15 | getCurrentPosition()
16 | expect(window.navigator.geolocation.getCurrentPosition.calls.mostRecent().args[2]).toEqual(
17 | jasmine.objectContaining({
18 | maximumAge: 0,
19 | timeout: 10000,
20 | enableHighAccuracy: true
21 | })
22 | )
23 | })
24 |
25 | it('should resolve with Geolocation', (done) => {
26 | getCurrentPosition().then((resp) => {
27 | expect(resp).toEqual(positionTemplate)
28 | done()
29 | })
30 | })
31 | })
32 |
33 | describe('error', () => {
34 | beforeEach(() => {
35 | spyOn(window.navigator.geolocation, 'getCurrentPosition').and.callFake((success, error) =>
36 | error(new Error('Location error'))
37 | )
38 | })
39 |
40 | it('should reject with text message', (done) => {
41 | getCurrentPosition().catch((resp) => {
42 | expect(resp).toEqual(jasmine.any(String))
43 | done()
44 | })
45 | })
46 | })
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/src/scope/Device.js:
--------------------------------------------------------------------------------
1 | import Scope from './Scope'
2 | import Property from '../entity/Property'
3 | import Action from '../entity/Action'
4 | import Location from '../entity/Location'
5 | import symbols from '../symbols'
6 | import { mixinResources } from '../util/mixin'
7 |
8 | /**
9 | * Mixin with all the top-level Application resources.
10 | *
11 | * @mixin
12 | */
13 | const DeviceAccess = mixinResources([
14 | Property, // CRUD
15 | Action, // CRUD
16 | Location // CRUD
17 | ])
18 |
19 | /**
20 | * Device is the Scope that represents an active/smart Thng. It can only
21 | * essentially update itself and its nested resources (e.g. Property, Location,
22 | * Action).
23 | *
24 | * @extends Scope
25 | */
26 | export default class Device extends DeviceAccess(Scope) {
27 | /**
28 | * Creates an instance of Device.
29 | *
30 | * @param {string} apiKey - API Key of scope
31 | * @param {Object} [data={}] - Optional device data
32 | */
33 | constructor (apiKey, data = {}) {
34 | super(apiKey, data)
35 |
36 | this.initPromise = super
37 | .readAccess()
38 | .then((access) => {
39 | this.id = access.actor.id
40 | this[symbols.path] = this._getPath()
41 | })
42 | .then(() => this.read())
43 | }
44 |
45 | /**
46 | * Read the Thng's data asynchronously.
47 | *
48 | * @returns {Promise}
49 | */
50 | init () {
51 | return this.initPromise
52 | }
53 |
54 | // PRIVATE
55 |
56 | /**
57 | * Return device thng endpoint.
58 | *
59 | * @return {string}
60 | */
61 | _getPath () {
62 | return `/thngs/${this.id}`
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/unit/outDated/entity/Product.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Resource from '../../../src/resource/Resource'
3 | import Product from '../../../src/entity/Product'
4 | import mockApi from '../../helpers/apiMock'
5 | import paths from '../../helpers/paths'
6 | import { dummyScope, dummyResource } from '../../helpers/dummy'
7 | import { productTemplate } from '../../helpers/data'
8 |
9 | let productResource
10 | let product
11 |
12 | describe('Product', () => {
13 | mockApi()
14 |
15 | describe('resourceFactory', () => {
16 | beforeEach(() => {
17 | const scope = Object.assign(dummyScope(), Product.resourceFactory())
18 | productResource = scope.product(productTemplate.id)
19 | })
20 |
21 | it('should create new Product resource', () => {
22 | expect(productResource instanceof Resource).toBe(true)
23 | expect(productResource.type).toBe(Product)
24 | expect(productResource.path).toEqual(`${paths.products}/${productTemplate.id}`)
25 | })
26 |
27 | it('should have nested property resource', () => {
28 | expect(productResource.property).toBeDefined()
29 | })
30 |
31 | it('should have nested action resource', () => {
32 | expect(productResource.action).toBeDefined()
33 | })
34 | })
35 |
36 | describe('access', () => {
37 | beforeEach(() => {
38 | product = new Product(dummyResource())
39 | })
40 |
41 | it('should have property resource', () => {
42 | expect(product.property).toBeDefined()
43 | })
44 |
45 | it('should have action resource', () => {
46 | expect(product.action).toBeDefined()
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/src/entity/Collection.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Thng from './Thng'
3 | import Action from './Action'
4 | import Resource from '../resource/Resource'
5 | import mixin, { mixinResources } from '../util/mixin'
6 |
7 | const path = '/collections'
8 | const CollectionResources = mixinResources([
9 | Thng,
10 | Action
11 | // Collection // Read explanation below.
12 | ])
13 |
14 | /**
15 | * Represents a Collection entity object. Collection has nested Collections
16 | * sub-resources. The workaround for the circular dependency is to only add
17 | * the Collection resource mixin after the class definition. This is different
18 | * than baking it in the parent Class Expression mixin
19 | * (i.e. CollectionResources) as the method is attached to the Collection
20 | * prototype, rather than the extended Entity class. Though, given the JS
21 | * prototype chain, there is no difference for the end user.
22 | *
23 | * @extends Entity
24 | */
25 | export default class Collection extends CollectionResources(Entity) {
26 | /**
27 | * Return simple resource factory for Collections.
28 | *
29 | * @static
30 | * @return {{collection: Function}}
31 | */
32 | static resourceFactory () {
33 | return {
34 | collection (id) {
35 | // Explicitly add Collection resource mixin to nested resource.
36 | return Object.assign(
37 | Resource.factoryFor(Collection, path, CollectionResources).call(this, id),
38 | Collection.resourceFactory()
39 | )
40 | }
41 | }
42 | }
43 | }
44 |
45 | // Explicitly add Collection resource mixin to Collection.
46 | mixin(Collection.resourceFactory())(Collection)
47 |
--------------------------------------------------------------------------------
/test/integration/entity/applicationRedirector.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Application Redirector', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read the application Redirector', async () => {
14 | api
15 | .get('/projects/projectId/applications/applicationId/redirector')
16 | .reply(200, { rules: [] })
17 | const res = await scope.project('projectId').application('applicationId').redirector().read()
18 |
19 | expect(res).to.be.an('object')
20 | expect(res.rules).to.be.an('array')
21 | })
22 |
23 | it('should update the application redirector', async () => {
24 | const payload = {
25 | rules: [{ match: 'thng.name=test' }]
26 | }
27 | api.put('/projects/projectId/applications/applicationId/redirector').reply(200, payload)
28 | const res = await scope
29 | .project('projectId')
30 | .application('applicationId')
31 | .redirector()
32 | .update(payload)
33 |
34 | expect(res.rules).to.deep.equal(payload.rules)
35 | })
36 |
37 | it('should delete the application redirector', async () => {
38 | api.delete('/projects/projectId/applications/applicationId/redirector').reply(204)
39 | const res = await scope
40 | .project('projectId')
41 | .application('applicationId')
42 | .redirector()
43 | .delete()
44 |
45 | expect(res).to.not.exist
46 | })
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/src/util/buildParams.js:
--------------------------------------------------------------------------------
1 | import isPlainObject from 'lodash-es/isPlainObject'
2 |
3 | const SPECIALS = ['&', '|', '!', '>', '<', '=', '~', '(', ')', ',']
4 | const SPECIALS_REGEXP = new RegExp(`[${SPECIALS.join('\\')}]`, 'g')
5 | const SPECIALS_ESCAPE = '\\$&'
6 |
7 | /**
8 | * Build url safe parameter string if an object provided.
9 | *
10 | * @export
11 | * @param {(Object | string)} [params] key-value object or final query string
12 | * @param {boolean} [useEncoding] whether to skip encoding
13 | * @returns {string}
14 | */
15 | export default function buildParams (params = {}, useEncoding = true) {
16 | return isPlainObject(params)
17 | ? Object.entries(params).map(buildParam(useEncoding)).join('&')
18 | : params
19 | }
20 |
21 | /**
22 | * Returns reducer function that adds the encoded key-value params to
23 | * accumulator.
24 | *
25 | * @param {boolean} useEncoding
26 | * @returns {Function}
27 | */
28 | function buildParam (useEncoding) {
29 | const encode = uriEncoder(useEncoding)
30 | return ([key, value]) => {
31 | return `${encode(key)}=${encode(buildParams(value))}`
32 | }
33 | }
34 |
35 | /**
36 | * Returns function that encodes values using encodeURIComponent.
37 | *
38 | * @param {boolean} useEncoding
39 | * @returns {Function}
40 | */
41 | function uriEncoder (useEncoding) {
42 | return (value) => (useEncoding ? encodeURIComponent(value) : escapeSpecials(value))
43 | }
44 |
45 | /**
46 | * Escape special characters in value with the backslash (\) character.
47 | *
48 | * @param {string} value
49 | * @returns {string}
50 | */
51 | function escapeSpecials (value) {
52 | return value.replace(SPECIALS_REGEXP, SPECIALS_ESCAPE)
53 | }
54 |
--------------------------------------------------------------------------------
/src/util/callback.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Apply error-first callback if available.
3 | *
4 | * @param {function} callback - Error-first callback
5 | * @returns {function} - Response handler function
6 | */
7 | export function success (callback) {
8 | return (response) => {
9 | if (callback) callback(null, response)
10 | return response
11 | }
12 | }
13 |
14 | /**
15 | * Apply error-first callback with error if available.
16 | *
17 | * @param {function} callback - Error-first callback
18 | * @returns {function} - Response handler function
19 | */
20 | export function failure (callback) {
21 | return (err) => {
22 | if (callback) callback(err)
23 |
24 | if (!err) {
25 | throw new Error('No error message available, err was:', err)
26 | }
27 |
28 | // Native Error?
29 | if (err.name && err.name.includes('Error')) {
30 | throw err
31 | }
32 |
33 | // If a Fetch API response, get the body text
34 | if (typeof err.ok !== 'undefined' && !err.ok) {
35 | // Must use 'then' instead of an async function here, or a client-side
36 | // 'catch' will pass a rejected Promise instead of an error.
37 | return err.text().then((body) => Promise.reject(body))
38 | }
39 |
40 | // If it's text instead of an already-parsed JSON object
41 | if (typeof err === 'string') {
42 | try {
43 | err = JSON.parse(err)
44 | } catch (e) {
45 | // The error text is not JSON
46 | }
47 | }
48 |
49 | // Throw a native Error to play nicer with error handling/retry libraries
50 | // Bonus: no need to check both e.message and e.errors
51 | throw new Error(err.message || JSON.stringify(err))
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/integration/entity/tasks.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Tasks', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create a task', async () => {
14 | const payload = {
15 | type: 'POPULATING',
16 | inputParameters: {
17 | type: 'FIXED_AMOUNT',
18 | generateThngs: true,
19 | generateRedirections: true,
20 | defaultRedirectUrl: 'https://google.com',
21 | thngTemplate: { name: 'E2E Test Thng' },
22 | shortDomain: 'tn.gg',
23 | quantity: 1,
24 | shortIdTemplate: { type: 'THNG_ID' }
25 | }
26 | }
27 | api.post('/batches/batchId/tasks', payload).reply(202)
28 | const res = await scope.batch('batchId').task().create(payload, { fullResponse: true })
29 |
30 | expect(typeof res).to.equal('object')
31 | expect(res.status).to.equal(202)
32 | })
33 |
34 | it('should read all tasks', async () => {
35 | api.get('/batches/batchId/tasks').reply(200, [{ id: 'taskId' }])
36 | const res = await scope.batch('batchId').task().read()
37 |
38 | expect(res).to.be.an('array')
39 | expect(res).to.have.length.gte(1)
40 | })
41 |
42 | it('should read a task', async () => {
43 | api.get('/batches/batchId/tasks/taskId').reply(200, { id: 'taskId' })
44 | const res = await scope.batch('batchId').task('taskId').read()
45 |
46 | expect(res).to.be.an('object')
47 | expect(res.id).to.equal('taskId')
48 | })
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/test/integration/entity/projects.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Projects', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create a project', async () => {
14 | const payload = { name: 'Test Project' }
15 | api.post('/projects', payload).reply(201, payload)
16 |
17 | const res = await scope.project().create(payload)
18 |
19 | expect(res).to.be.an('object')
20 | expect(res.name).to.equal('Test Project')
21 | })
22 |
23 | it('should read all projects', async () => {
24 | api.get('/projects').reply(200, [{ id: 'projectId' }])
25 | const res = await scope.project().read()
26 |
27 | expect(res).to.be.an('array')
28 | expect(res).to.have.length.gte(1)
29 | })
30 |
31 | it('should read a project', async () => {
32 | api.get('/projects/projectId').reply(200, { id: 'projectId' })
33 | const res = await scope.project('projectId').read()
34 |
35 | expect(res).to.be.an('object')
36 | expect(res.id).to.be.a('string')
37 | })
38 |
39 | it('should update a project', async () => {
40 | const payload = { tags: ['updated'] }
41 | api.put('/projects/projectId', payload).reply(200, payload)
42 | const res = await scope.project('projectId').update(payload)
43 |
44 | expect(res).to.be.an('object')
45 | expect(res.tags).to.deep.equal(['updated'])
46 | })
47 |
48 | it('should delete a project', async () => {
49 | api.delete('/projects/projectId').reply(200)
50 | await scope.project('projectId').delete()
51 | })
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/test/integration/entity/roles.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Roles', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all roles', async () => {
14 | api.get('/roles').reply(200, [{ id: 'roleId' }])
15 | const res = await scope.role().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(1)
19 | })
20 |
21 | if (scopeType === 'operator') {
22 | it('should create a role', async () => {
23 | const payload = { name: 'Test Role' }
24 | api.post('/roles', payload).reply(201, payload)
25 | const res = await scope.role().create(payload)
26 |
27 | expect(res).to.be.an('object')
28 | expect(res.name).to.equal('Test Role')
29 | })
30 |
31 | it('should read a role', async () => {
32 | api.get('/roles/roleId').reply(200, { id: 'roleId' })
33 | const res = await scope.role('roleId').read()
34 |
35 | expect(res).to.be.an('object')
36 | expect(res.id).to.equal('roleId')
37 | })
38 |
39 | it('should update a role', async () => {
40 | const payload = { description: 'updated' }
41 | api.put('/roles/roleId', payload).reply(200, payload)
42 | const res = await scope.role('roleId').update(payload)
43 |
44 | expect(res).to.be.an('object')
45 | expect(res.description).to.equal(payload.description)
46 | })
47 |
48 | it('should delete a role', async () => {
49 | api.delete('/roles/roleId').reply(200)
50 | await scope.role('roleId').delete()
51 | })
52 | }
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An interceptor implements a request and/or response handlers that are called
3 | * before and after each request, respectively.
4 | *
5 | * @typedef {Object} Interceptor
6 | * @property {Function} request - Function to run before the request is sent
7 | * @property {Function} response - Function to run after the response is received
8 | */
9 |
10 | /**
11 | * Settings can be applied globally or for individual requests.
12 | * Available options are provided below:
13 | *
14 | * @typedef {Object} Settings
15 | * @property {string} apiUrl - API url of request
16 | * @property {string} url - Url relative to `apiUrl`
17 | * @property {string} method - HTTP Method of request
18 | * @property {string} apiKey - API Key to use with request
19 | * @property {boolean} fullResponse - Flags if request should remain unwrapped
20 | * @property {boolean} geolocation - Flags if action creation should use the Web
21 | * Geolocation API
22 | * @property {number} timeout - Timeout for request
23 | * @property {Object} headers - Headers to send with request
24 | * @property {Interceptor[]} interceptors - List of request/response interceptors
25 | * @property {string} defaultShortDomain - Default short domain to use for redirections
26 | */
27 |
28 | /**
29 | * Default settings. Never change.
30 | *
31 | * @type {Settings}
32 | */
33 | const defaultSettings = {
34 | apiUrl: 'https://api.evrythng.io/v2',
35 | apiKey: '',
36 | fullResponse: false,
37 | geolocation: true,
38 | timeout: 0,
39 | headers: {
40 | 'content-type': 'application/json'
41 | },
42 | interceptors: [],
43 | defaultShortDomain: 'tn.gg',
44 | apiVersion: 2,
45 | region: 'us'
46 | }
47 |
48 | // Initialize settings with defaults.
49 | const settings = Object.assign({}, defaultSettings)
50 |
51 | export default settings
52 |
--------------------------------------------------------------------------------
/test/integration/entity/thngs.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Thngs', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create a Thng', async () => {
14 | const payload = { name: 'Test Thng' }
15 | api.post('/thngs', payload).reply(201, payload)
16 | const res = await scope.thng().create(payload)
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.name).to.equal(payload.name)
20 | })
21 |
22 | it('should read a Thng', async () => {
23 | api.get('/thngs/thngId').reply(200, { id: 'thngId' })
24 | const res = await scope.thng('thngId').read()
25 |
26 | expect(res).to.be.an('object')
27 | expect(res.id).to.equal('thngId')
28 | })
29 |
30 | it('should read all Thngs', async () => {
31 | api.get('/thngs').reply(200, [{ id: 'thngId' }])
32 | const res = await scope.thng().read()
33 |
34 | expect(res).to.be.an('array')
35 | expect(res).to.have.length.gte(1)
36 | })
37 |
38 | it('should update a Thng', async () => {
39 | const payload = { tags: ['updated'] }
40 | api.put('/thngs/thngId', payload).reply(200, payload)
41 | const res = await scope.thng('thngId').update(payload)
42 |
43 | expect(res).to.be.an('object')
44 | expect(res.tags).to.deep.equal(['updated'])
45 | })
46 |
47 | if (['operator', 'trustedApp'].includes(scopeType)) {
48 | it('should delete a Thng', async () => {
49 | api.delete('/thngs/thngId').reply(200)
50 | const res = await scope.thng('thngId').delete()
51 | expect(res).to.not.exist
52 | })
53 | }
54 | })
55 | }
56 |
--------------------------------------------------------------------------------
/test/integration/entity/places.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Places', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all places', async () => {
14 | api.get('/places').reply(200, [{ id: 'placeId' }])
15 | const res = await scope.place().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(0)
19 | })
20 |
21 | if (['operator', 'trustedApp'].includes(scopeType)) {
22 | it('should create a place', async () => {
23 | const payload = { name: 'Test Place' }
24 | api.post('/places', payload).reply(201, payload)
25 | const res = await scope.place().create(payload)
26 |
27 | expect(res).to.be.an('object')
28 | expect(res.name).to.equal(payload.name)
29 | })
30 |
31 | it('should read a place', async () => {
32 | api.get('/places/placeId').reply(200, { id: 'placeId' })
33 | const res = await scope.place('placeId').read()
34 |
35 | expect(res).to.be.an('object')
36 | expect(res.id).to.equal('placeId')
37 | })
38 |
39 | it('should update a place', async () => {
40 | const payload = { tags: ['updated'] }
41 | api.put('/places/placeId', payload).reply(200, payload)
42 | const res = await scope.place('placeId').update(payload)
43 |
44 | expect(res).to.be.an('object')
45 | expect(res.tags).to.deep.equal(['updated'])
46 | })
47 |
48 | it('should delete a place', async () => {
49 | api.delete('/places/placeId').reply(200)
50 | await scope.place('placeId').delete()
51 | })
52 | }
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/test/integration/entity/products.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Products', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create a product', async () => {
14 | const payload = { name: 'Test Product' }
15 | api.post('/products', payload).reply(201, payload)
16 | const res = await scope.product().create(payload)
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.name).to.equal(payload.name)
20 | })
21 |
22 | it('should read a product', async () => {
23 | api.get('/products/productId').reply(200, { id: 'productId' })
24 | const res = await scope.product('productId').read()
25 |
26 | expect(res).to.be.an('object')
27 | expect(res.id).to.equal('productId')
28 | })
29 |
30 | it('should read all products', async () => {
31 | api.get('/products').reply(200, [{ id: 'productId' }])
32 | const res = await scope.product().read()
33 |
34 | expect(res).to.be.an('array')
35 | expect(res).to.have.length.gte(1)
36 | })
37 |
38 | it('should update a product', async () => {
39 | const payload = { tags: ['updated'] }
40 | api.put('/products/productId', payload).reply(200, payload)
41 | const res = await scope.product('productId').update(payload)
42 |
43 | expect(res).to.be.an('object')
44 | expect(res.tags).to.deep.equal(['updated'])
45 | })
46 |
47 | if (['operator', 'trustedApp'].includes(scopeType)) {
48 | it('should delete a product', async () => {
49 | api.delete('/products/productId').reply(200)
50 | await scope.product('productId').delete()
51 | })
52 | }
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/src/entity/ReactorLog.js:
--------------------------------------------------------------------------------
1 | import Entity from './Entity'
2 | import Resource from '../resource/Resource'
3 | import isString from 'lodash-es/isString'
4 |
5 | const path = '/reactor/logs'
6 |
7 | /**
8 | * Represents a ReactorLog entity object.
9 | *
10 | * @extends Entity
11 | */
12 | export default class ReactorLog extends Entity {
13 | static resourceFactory () {
14 | return {
15 | /**
16 | * ReactorLog Entity resource. If using with Operator, app and project
17 | * details are required.
18 | *
19 | * @param {string} projectId - Project ID, if using with Operator scope.
20 | * @param {string} applicationId - Application ID, if using with Operator scope.
21 | * @returns {Object}
22 | */
23 | reactorLog (projectId, applicationId) {
24 | if (isString(projectId) && !isString(applicationId)) {
25 | throw new Error('When using with Operator, projectId and applicationId are required')
26 | }
27 |
28 | const useProjectId = projectId || this.project
29 | const useApplicationId = applicationId || this.app
30 |
31 | // Find the path to this application
32 | const appPath = `/projects/${useProjectId}/applications/${useApplicationId}`
33 |
34 | return Object.assign(Resource.factoryFor(ReactorLog, appPath + path).call(this), {
35 | create (...args) {
36 | return createLogs.call(this, ...args)
37 | }
38 | })
39 | }
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * Use bulk endpoint when creating array of logs.
46 | *
47 | * @param {Object} data - Logs payload.
48 | * @param {Object} options - Optional request options.
49 | * @return {Promise}
50 | */
51 | function createLogs (data, options = {}) {
52 | const logs = Array.isArray(data) ? data : [data]
53 |
54 | // Change URL for bulk creation
55 | options.url = `${this.path}/bulk`
56 |
57 | return Resource.prototype.create.call(this, logs, options)
58 | }
59 |
--------------------------------------------------------------------------------
/test/integration/entity/collections.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Collections', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create a collection', async () => {
14 | const payload = { name: 'Test Collection' }
15 | api.post('/collections', payload).reply(200, payload)
16 | const res = await scope.collection().create(payload)
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.name).to.equal('Test Collection')
20 | })
21 |
22 | it('should read a collection', async () => {
23 | api.get('/collections/collectionId').reply(200, { id: 'collectionId' })
24 | const res = await scope.collection('collectionId').read()
25 |
26 | expect(res).to.be.an('object')
27 | expect(res.id).to.equal('collectionId')
28 | })
29 |
30 | it('should read all collections', async () => {
31 | api.get('/collections').reply(200, [{ id: 'collectionId' }])
32 | const res = await scope.collection().read()
33 |
34 | expect(res).to.be.an('array')
35 | expect(res).to.have.length.gte(1)
36 | })
37 |
38 | it('should update a collection', async () => {
39 | const payload = { tags: ['updated'] }
40 | api.put('/collections/collectionId').reply(200, { tags: ['updated'] })
41 | const res = await scope.collection('collectionId').update(payload)
42 |
43 | expect(res).to.be.an('object')
44 | expect(res.tags).to.deep.equal(['updated'])
45 | })
46 |
47 | if (['operator', 'trustedApp'].includes(scopeType)) {
48 | it('should delete a collection', async () => {
49 | api.delete('/collections/collectionId').reply(200)
50 | const res = await scope.collection('collectionId').delete()
51 |
52 | expect(res).to.not.exist
53 | })
54 | }
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/test/unit/outDated/scope/Scope.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jasmine */
2 | import Scope from '../../../src/scope/Scope'
3 | import symbols from '../../../src/symbols'
4 | import fetchMock from 'fetch-mock'
5 | import mockApi from '../../helpers/apiMock'
6 | import apiUrl from '../../helpers/apiUrl'
7 | import paths from '../../helpers/paths'
8 | import { apiKey, operatorTemplate } from '../../helpers/data'
9 |
10 | let scope
11 |
12 | describe('Scope', () => {
13 | mockApi()
14 |
15 | describe('constructor', () => {
16 | describe('invalid', () => {
17 | it('should need an API key', () => {
18 | const badCall = () => new Scope()
19 | expect(badCall).toThrow()
20 | })
21 |
22 | it('should need a String API Key', () => {
23 | const badCall = () => new Scope({ apiKey })
24 | expect(badCall).toThrow()
25 | })
26 | })
27 |
28 | describe('valid', () => {
29 | beforeEach(() => {
30 | scope = new Scope(apiKey, operatorTemplate)
31 | })
32 |
33 | it('should have API Key', () => {
34 | expect(scope.apiKey).toEqual(apiKey)
35 | })
36 |
37 | it('should extend document with any pre-provided data', () => {
38 | expect(scope.id).toEqual(operatorTemplate.id)
39 | expect(scope.email).toEqual(operatorTemplate.email)
40 | })
41 |
42 | it('should expose init promise', () => {
43 | expect(scope[symbols.init]).toBeDefined()
44 | expect(scope[symbols.init] instanceof Promise).toBe(true)
45 | })
46 |
47 | it('should fetch scope access using scope apiKey', (done) => {
48 | scope[symbols.init].then(() => {
49 | expect(fetchMock.lastUrl()).toEqual(apiUrl(paths.access))
50 | expect(fetchMock.lastOptions()).toEqual(
51 | jasmine.objectContaining({
52 | headers: jasmine.objectContaining({ authorization: apiKey })
53 | })
54 | )
55 | done()
56 | })
57 | })
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/test/integration/entity/files.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const nock = require('nock')
3 | const { getScope, mockApi } = require('../util')
4 |
5 | module.exports = (scopeType, apiUrl) => {
6 | describe('Files', () => {
7 | let scope, api
8 |
9 | before(() => {
10 | scope = getScope(scopeType)
11 | api = mockApi(apiUrl)
12 | })
13 | it('should create a file', async () => {
14 | const payload = { name: 'TestFile.txt' }
15 | api.post('/files', payload).reply(201, payload)
16 | const res = await scope.file().create(payload)
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.name).to.equal(payload.name)
20 | })
21 |
22 | it('should read all files', async () => {
23 | api.get('/files').reply(200, [{ id: 'fileId' }])
24 | const res = await scope.file().read()
25 |
26 | expect(res).to.be.an('array')
27 | expect(res).to.have.length.gte(1)
28 | })
29 |
30 | it('should read a file', async () => {
31 | api.get('/files/fileId').reply(200, { id: 'fileId' })
32 | const res = await scope.file('fileId').read()
33 |
34 | expect(res).to.be.an('object')
35 | expect(res.id).to.equal('fileId')
36 | })
37 |
38 | it.skip('should upload file content - text', async () => {
39 | const fileData = 'This is example text file content'
40 |
41 | api.get('/files/fileId').reply(200, { id: 'fileId', uploadUrl: 'https://s3.amazonaws.com' })
42 | await scope.file('fileId').upload(fileData)
43 |
44 | api.get('/files/fileId').reply(200, { contentUrl: 'https://s3.amazonaws.com/upload' })
45 | const resource = await scope.file('fileId').read()
46 | nock('https://s3.amazonaws.com').get('/upload').reply(200, fileData)
47 | const readData = await fetch(resource.contentUrl).then((res) => res.text())
48 |
49 | expect(readData).to.equal(fileData)
50 | })
51 |
52 | it('should delete a file', async () => {
53 | api.delete('/files/fileId').reply(200)
54 | await scope.file('fileId').delete()
55 | })
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/test/integration/misc/alias.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 | const evrythng = require('../../../')
4 |
5 | module.exports = (scopeType, url) => {
6 | describe('alias', () => {
7 | let scope, api
8 |
9 | before(() => {
10 | scope = getScope(scopeType)
11 | api = mockApi(url)
12 | })
13 |
14 | it("should alias 'product' to 'sku' for Operator scope", async () => {
15 | evrythng.alias({ product: 'sku' }, 'Operator')
16 |
17 | expect(scope.sku).to.be.a('function')
18 | })
19 |
20 | it("should use the alias to read all 'sku's", async () => {
21 | api.get('/products').reply(200, [{ id: 'productId' }])
22 | const skus = await scope.sku().read()
23 |
24 | expect(skus).to.be.an('array')
25 | expect(skus).to.have.length.gte(0)
26 | })
27 |
28 | it("should use the alias to create a 'sku'", async () => {
29 | const payload = { name: 'Example SKU' }
30 | api.post('/products', payload).reply(200, { id: 'productId' })
31 | const res = await scope.sku().create(payload)
32 |
33 | expect(res).to.be.an('object')
34 | expect(res.id).to.be.a('string')
35 | })
36 |
37 | it("should use the alias to read a 'sku'", async () => {
38 | api.get('/products/productId').reply(200, { id: 'productId' })
39 | const res = await scope.sku('productId').read()
40 |
41 | expect(res).to.be.an('object')
42 | expect(res.id).to.equal('productId')
43 | })
44 |
45 | it("should use the alias to update a 'sku'", async () => {
46 | const payload = { tags: ['updated'] }
47 | api.put('/products/productId', payload).reply(200, { id: 'productId' })
48 | const res = await scope.sku('productId').update(payload)
49 |
50 | expect(res).to.be.an('object')
51 | expect(res.id).to.equal('productId')
52 | })
53 |
54 | it("should use the alias to delete a 'sku'", async () => {
55 | api.delete('/products/productId').reply(200)
56 | await scope.sku('productId').delete()
57 | })
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/test/integration/entity/applications.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Applications', () => {
6 | let scope, api
7 |
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create an application', async () => {
14 | const payload = { name: 'Application Name', socialNetworks: {} }
15 | api.post('/projects/projectId/applications').reply(201, payload)
16 | const res = await scope.project('projectId').application().create(payload)
17 |
18 | expect(res).to.be.an('object')
19 | expect(res.name).to.be.a('string')
20 | })
21 |
22 | it('should read all applications', async () => {
23 | api.get('/projects/projectId/applications').reply(200, [{ id: 'applicationId' }])
24 | const res = await scope.project('projectId').application().read()
25 |
26 | expect(res).to.be.an('array')
27 | expect(res).to.have.length.gte(1)
28 | })
29 |
30 | it('should read an application', async () => {
31 | api.get('/projects/projectId/applications/applicationId').reply(200, { id: 'applicationId' })
32 | const res = await scope.project('projectId').application('applicationId').read()
33 |
34 | expect(res).to.be.an('object')
35 | expect(res.id).to.equal('applicationId')
36 | })
37 |
38 | it('should update an application', async () => {
39 | const payload = { tags: ['updated'] }
40 | api.put('/projects/projectId/applications/applicationId').reply(200, payload)
41 | const res = await scope.project('projectId').application('applicationId').update(payload)
42 |
43 | expect(res).to.be.an('object')
44 | expect(res.tags).to.deep.equal(payload.tags)
45 | })
46 |
47 | it('should delete an application', async () => {
48 | api.delete('/projects/projectId/applications/applicationId').reply(200)
49 | await scope.project('projectId').application('applicationId').delete()
50 | })
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/test/integration/entity/adiOrders.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('ADI Orders', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should create an ADI Order', async () => {
14 | const payload = {
15 | ids: ['serial1', 'serial2'],
16 | purchaseOrder: '234567890',
17 | metadata: {
18 | identifierKey: 'gs1:21',
19 | customFields: { factory: '0400321' },
20 | tags: ['factory:0400321'],
21 | shortDomain: 'tn.gg',
22 | defaultRedirectUrl: 'https://evrythng.com?id={shortId}'
23 | },
24 | identifiers: { internalId: 'X7JF' },
25 | tags: ['X7JF']
26 | }
27 |
28 | api.post('/adis/orders', payload).reply(201, { id: 'UEp4rDGsnpCAF6xABbys5Amc' })
29 | const res = await scope.adiOrder().create(payload)
30 |
31 | expect(res).to.be.an('object')
32 | expect(res.id).to.have.length(24)
33 | })
34 |
35 | it('should read an ADI Order by ID', async () => {
36 | api.get('/adis/orders/adiOrderId').reply(200, { id: 'adiOrderId' })
37 | const res = await scope.adiOrder('adiOrderId').read()
38 |
39 | expect(res).to.be.an('object')
40 | expect(res.id).to.be.a('string')
41 | })
42 |
43 | it('should create an ADI Order event', async () => {
44 | const payload = {
45 | metadata: {
46 | type: 'encodings',
47 | tags: ['example']
48 | },
49 | ids: ['serial1', 'serial2'],
50 | customFields: { internalId: 'X7JF' },
51 | tags: ['X7JF']
52 | }
53 |
54 | api
55 | .post('/adis/orders/adiOrderId/events', payload)
56 | .reply(201, { id: 'UrCPgMhMMmPEY6awwEf6gKfb' })
57 | const res = await scope.adiOrder('adiOrderId').event().create(payload)
58 |
59 | expect(res).to.be.an('object')
60 | expect(res.id).to.be.a('string')
61 | })
62 | })
63 | }
64 |
--------------------------------------------------------------------------------
/test/integration/entity/actionTypes.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, url) => {
5 | describe('Action Types', () => {
6 | let scope, api
7 |
8 | before(() => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it('should read all action types', async () => {
14 | api.get('/actions').reply(200, [{ name: '_CustomType' }])
15 | const res = await scope.actionType().read()
16 |
17 | expect(res).to.be.an('array')
18 | expect(res).to.have.length.gte(1)
19 | })
20 |
21 | if (['operator', 'trustedApp'].includes(scopeType)) {
22 | it('should create an action type', async () => {
23 | const payload = { name: '_CustomType' }
24 | api.post('/actions', payload).reply(201, { name: '_CustomType' })
25 | const res = await scope.actionType().create(payload)
26 |
27 | expect(res).to.be.an('object')
28 | expect(res.name).to.equal(payload.name)
29 | })
30 |
31 | it('should read an action type', async () => {
32 | api.get('/actions?filter=name%3D_CustomType').reply(200, [{ name: '_CustomType' }])
33 | const res = await scope.actionType('_CustomType').read()
34 |
35 | expect(res).to.be.an('object')
36 | expect(res.name).to.equal('_CustomType')
37 | })
38 | }
39 |
40 | if (scopeType === 'operator') {
41 | it('should update an action type', async () => {
42 | const payload = { tags: ['updated'] }
43 | api.put('/actions/_CustomType', payload).reply(200, payload)
44 | const res = await scope.actionType('_CustomType').update(payload)
45 |
46 | expect(res).to.be.an('object')
47 | expect(res.tags).to.deep.equal(payload.tags)
48 | })
49 | }
50 |
51 | if (['operator', 'trustedApp'].includes(scopeType)) {
52 | it('should delete an actionType', async () => {
53 | api.delete('/actions/_CustomType').reply(200)
54 | await scope.actionType('_CustomType').delete()
55 | })
56 | }
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/src/scope/AccessToken.js:
--------------------------------------------------------------------------------
1 | import Scope from './Scope'
2 | import Account from '../entity/Account'
3 | import ADIOrder from '../entity/ADIOrder'
4 | import Product from '../entity/Product'
5 | import Thng from '../entity/Thng'
6 | import Collection from '../entity/Collection'
7 | import Action from '../entity/Action'
8 | import ActionType from '../entity/ActionType'
9 | import Project from '../entity/Project'
10 | import PurchaseOrder from '../entity/PurchaseOrder'
11 | import Redirector from '../entity/Redirector'
12 | import ShipmentNotice from '../entity/ShipmentNotice'
13 | import Place from '../entity/Place'
14 | import File from '../entity/File'
15 | import AccessPolicy from '../entity/AccessPolicy'
16 | import AccessTokenEntity from '../entity/AccessToken'
17 | import Me from '../entity/Me'
18 | import { mixinResources } from '../util/mixin'
19 |
20 | /**
21 | * Mixin with all AccessToken resources.
22 | *
23 | * @mixin
24 | */
25 | const AccessTokenResources = mixinResources([
26 | AccessPolicy,
27 | AccessTokenEntity,
28 | Account,
29 | Action,
30 | ActionType,
31 | ADIOrder,
32 | Collection,
33 | File,
34 | Me,
35 | Place,
36 | Product,
37 | Project,
38 | PurchaseOrder,
39 | Redirector,
40 | ShipmentNotice,
41 | Thng
42 | ])
43 |
44 | /**
45 | * AccessToken is a Scope type for the v2 API with the potential to manage any account resources, depending on the API key's
46 | * permissions. It represents an API access for a specific purpose, instead of a type of Actor, such as an Operator.
47 | *
48 | * @extends Scope
49 | * @mixes AccessTokenResources
50 | */
51 | export default class AccessToken extends AccessTokenResources(Scope) {
52 | /**
53 | * Creates an instance of AccessToken.
54 | *
55 | * @param {string} apiKey - API Key of scope
56 | * @param {Object} [data={}] - Optional data
57 | */
58 | constructor (apiKey, data = {}) {
59 | super(apiKey, data)
60 |
61 | this.initPromise = super.readAccess()
62 | }
63 |
64 | /**
65 | * Read the access token's data asynchronously.
66 | *
67 | * @returns {Promise}
68 | */
69 | init () {
70 | return this.initPromise
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/test/integration/entity/properties.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | module.exports = (scopeType, targetType, url) => {
5 | let scope, api
6 |
7 | describe(`Properties (${targetType})`, () => {
8 | before(async () => {
9 | scope = getScope(scopeType)
10 | api = mockApi(url)
11 | })
12 |
13 | it(`should create a ${targetType} property`, async () => {
14 | const payload = { key: 'temp_c', value: 42 }
15 | api.post(`/${targetType}s/targetId/properties`).reply(200, [payload])
16 | const res = await scope[targetType]('targetId').property().create(payload)
17 |
18 | expect(res).to.be.an('array')
19 | expect(res).to.have.length(1)
20 | })
21 |
22 | it(`should read all ${targetType} properties`, async () => {
23 | api.get(`/${targetType}s/targetId/properties`).reply(200, [{ key: 'temp_c', value: 42 }])
24 | const res = await scope[targetType]('targetId').property().read()
25 |
26 | expect(res).to.be.an('array')
27 | expect(res).to.have.length(1)
28 | })
29 |
30 | it(`should read a single ${targetType} property`, async () => {
31 | api.get(`/${targetType}s/targetId/properties/temp_c`).reply(200, [{ value: 42 }])
32 | const res = await scope[targetType]('targetId').property('temp_c').read()
33 |
34 | expect(res).to.be.an('array')
35 | expect(res).to.have.length(1)
36 | })
37 |
38 | it(`should update a single ${targetType} property`, async () => {
39 | api
40 | .put(`/${targetType}s/targetId/properties/temp_c`, [{ value: 43 }])
41 | .reply(200, [{ value: 43 }])
42 | const res = await scope[targetType]('targetId').property('temp_c').update(43)
43 |
44 | expect(res).to.be.an('array')
45 | expect(res[0].value).to.equal(43)
46 | })
47 |
48 | if (scopeType !== 'anonUser') {
49 | it(`should delete a single ${targetType} property`, async () => {
50 | api.delete(`/${targetType}s/targetId/properties/temp_c`).reply(200)
51 | await scope[targetType]('targetId').property('temp_c').delete()
52 | })
53 | }
54 | })
55 | }
56 |
--------------------------------------------------------------------------------
/test/integration/entity/shipmentNotice.spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require('chai')
2 | const { getScope, mockApi } = require('../util')
3 |
4 | const noticePayload = {
5 | asnId: '2343689643',
6 | version: '1',
7 | issueDate: '2019-06-19T16:39:57-08:00',
8 | transportation: 'Expedited Freight',
9 | parties: [
10 | {
11 | id: 'gs1:414:01251',
12 | type: 'ship-from'
13 | },
14 | {
15 | name: 'The Landmark, Shop No. G14',
16 | type: 'ship-to',
17 | address: {
18 | street: '113-114, Central',
19 | city: 'Hong Kong'
20 | }
21 | }
22 | ],
23 | tags: ['ongoing']
24 | }
25 |
26 | module.exports = (scopeType, url) => {
27 | describe('Shipment Notices', () => {
28 | let scope, api
29 |
30 | before(() => {
31 | scope = getScope(scopeType)
32 | api = mockApi(url)
33 | })
34 |
35 | it('should create a shipment notice', async () => {
36 | api.post('/shipmentNotices', noticePayload).reply(201, noticePayload)
37 |
38 | const res = await scope.shipmentNotice().create(noticePayload)
39 |
40 | expect(res).to.be.an('object')
41 | expect(res.asnId).to.equal(noticePayload.asnId)
42 | })
43 |
44 | it('should read a shipment notice', async () => {
45 | api.get('/shipmentNotices/shipmentNoticeId').reply(200, noticePayload)
46 |
47 | const res = await scope.shipmentNotice('shipmentNoticeId').read()
48 |
49 | expect(res).to.be.an('object')
50 | expect(res.asnId).to.equal(noticePayload.asnId)
51 | })
52 |
53 | it('should update a shipment notice', async () => {
54 | api.put('/shipmentNotices/shipmentNoticeId').reply(200, noticePayload)
55 |
56 | const res = await scope.shipmentNotice('shipmentNoticeId').update(noticePayload)
57 |
58 | expect(res).to.be.an('object')
59 | expect(res.tags).to.deep.equal(noticePayload.tags)
60 | })
61 |
62 | it('should delete a shipment notice', async () => {
63 | api.delete('/shipmentNotices/shipmentNoticeId').reply(204)
64 |
65 | const res = await scope.shipmentNotice('shipmentNoticeId').delete()
66 |
67 | expect(res).to.not.exist
68 | })
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/src/entity/Entity.js:
--------------------------------------------------------------------------------
1 | import Resource from '../resource/Resource'
2 | import symbols from '../symbols'
3 |
4 | /**
5 | * Entity is the base class for all types of entities in the EVRYTHNG API.
6 | * An Entity knows how to update and delete itself given that a resource is
7 | * provided.
8 | */
9 | export default class Entity {
10 | /**
11 | * Creates an new entity of given Resource. Optionally can be initialized
12 | * with pre-defined data.
13 | *
14 | * @param {Resource} resource - Resource owner of this entity.
15 | * @param {Object} [body] Optional entity data
16 | */
17 | constructor (resource, body = {}) {
18 | if (!(resource && resource instanceof Resource)) {
19 | throw new Error('Resource must be a Resource.')
20 | }
21 |
22 | // Define non-enumerable unique resource property so it's not copied over
23 | // in shallow copies of this (e.g. using Object.assign).
24 | this[symbols.resource] = resource
25 |
26 | // Extend Entity with data.
27 | Object.assign(this, body)
28 | }
29 |
30 | /**
31 | * Returns all enumerable properties.
32 | *
33 | * @returns {Object}
34 | */
35 | json () {
36 | return Object.entries(this).reduce((ret, [k, v]) => Object.assign(ret, { [k]: v }), {})
37 | }
38 |
39 | /**
40 | * Update itself by calling the update method of the owning resource and
41 | * passing the JSON representation of itself or the given body object.
42 | *
43 | * @param {Object} body - optional body, use self as default
44 | * @param {Function} callback - error-first callback
45 | * @returns {Promise.