@laboratoria/sdk-js 1.0.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/core.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import md5 from 'blueimp-md5';
2
- import currencies from './currencies';
3
- import roles from './roles';
4
2
  import { createModels, extendSchemaDefinitions } from './model';
5
3
  import schema from '../schemas/core.json';
6
4
 
7
5
  const extended = {
8
6
  Country: {
7
+ primaryKey: 'code',
9
8
  plural: 'countries',
9
+ searchProps: ['code', 'name'],
10
10
  },
11
11
  User: {
12
12
  primaryKey: 'uid',
@@ -122,39 +122,33 @@ const extended = {
122
122
  Contract: {
123
123
  inputProps: [
124
124
  'user',
125
- 'countryCode',
126
- 'currency',
125
+ 'country',
127
126
  'isEmployment',
128
- 'feeBasis',
129
127
  'feeAmount',
130
128
  'hoursPerWeek',
131
129
  'start',
132
130
  'end',
131
+ 'isTrial',
132
+ 'checkPilot',
133
+ 'source',
133
134
  ],
134
- properties: {
135
- countryCode: {
136
- enum: ['BR', 'CL', 'CO', 'MX', 'PE'],
137
- },
138
- currency: {
139
- enum: currencies,
140
- },
141
- },
135
+ searchProps: [
136
+ 'user.firstName',
137
+ 'user.lastName',
138
+ 'user.email',
139
+ ],
140
+ getOptionLabel: ({ id, user }) => `${user?.firstName} (id: ${id})`,
142
141
  },
143
142
  Gig: {
144
143
  inputProps: [
145
- // 'contract',
146
144
  'cohort',
147
145
  'user',
146
+ 'contract',
148
147
  'role',
149
148
  'hoursPerWeek',
150
149
  'start',
151
150
  'end',
152
151
  ],
153
- properties: {
154
- role: {
155
- enum: roles,
156
- },
157
- },
158
152
  },
159
153
  };
160
154
 
@@ -162,8 +156,3 @@ export const createAPI = (url, state) => createModels(url, state, {
162
156
  ...schema,
163
157
  definitions: extendSchemaDefinitions(schema, extended),
164
158
  });
165
-
166
- // const {
167
- // delete: _,
168
- // ...userAPI,
169
- // } = createModel(coreApiUrl, state, 'users', schemas.user);
package/lib/model.js CHANGED
@@ -1,6 +1,38 @@
1
1
  import { createClient } from './client.js';
2
2
 
3
3
 
4
+ const isRequiredOneToOneRelation = (schema, key) => (
5
+ !!schema.properties
6
+ && !!schema.properties[key]
7
+ && !!schema.properties[key].$ref
8
+ );
9
+
10
+ const isOptionalOneToOneRelation = (schema, key) => (
11
+ !!schema.properties
12
+ && !!schema.properties[key]
13
+ && Array.isArray(schema.properties[key].anyOf)
14
+ && !!schema.properties[key].anyOf[0]?.$ref
15
+ && schema.properties[key].anyOf[1]?.type === 'null'
16
+ );
17
+
18
+ // const isOneToOneRelation = (schema, key) => (
19
+ // isRequiredOneToOneRelation(schema, key)
20
+ // || isOptionalOneToOneRelation(schema, key)
21
+ // );
22
+
23
+ const isOneToManyRelation = (schema, key) => (
24
+ schema.properties
25
+ && schema.properties[key]
26
+ && schema.properties[key].type === 'array'
27
+ && !!schema.properties[key].items.$ref
28
+ );
29
+
30
+ // const isRelation = (schema, key) => (
31
+ // isOneToOneRelation(schema, key)
32
+ // || isOneToManyRelation(schema, key)
33
+ // );
34
+
35
+
4
36
  const createValidator = (schema) => {
5
37
  const properties = schema.properties || {};
6
38
  const inputProps = schema.inputProps || Object.keys(properties);
@@ -73,20 +105,12 @@ const createBuildURL = collectionName => (id, q) => {
73
105
 
74
106
  const serializeData = (data, schema) => {
75
107
  const hasInputProps = Array.isArray(schema.inputProps);
76
- const payload = Object.keys(data).reduce(
108
+ return Object.keys(data).reduce(
77
109
  (memo, key) => {
78
110
  if (hasInputProps && !schema.inputProps.includes(key)) {
79
111
  return memo;
80
112
  }
81
- const prop = (schema.properties || {})[key];
82
- const isOptionalRelation = (
83
- prop
84
- && Array.isArray(prop.anyOf)
85
- && prop.anyOf[0].$ref
86
- && prop.anyOf[1]
87
- && prop.anyOf[1].type === 'null'
88
- );
89
- if (isOptionalRelation && data[key] === null) {
113
+ if (isOptionalOneToOneRelation(schema, key) && data[key] === null) {
90
114
  return memo;
91
115
  }
92
116
  return {
@@ -96,36 +120,61 @@ const serializeData = (data, schema) => {
96
120
  },
97
121
  {},
98
122
  );
99
-
100
- // {
101
- // signupCohort: {
102
- // connect: { id: signupCohort.id },
103
- // },
104
- // }
105
-
106
- return payload;
107
123
  };
108
124
 
109
125
 
110
- export const createModel = (baseUrl, state, collectionName, schema = {}) => {
126
+ export const createModel = (baseUrl, state, collectionName, schema = {}, models = {}) => {
111
127
  const primaryKey = schema.primaryKey || 'id';
112
128
  const validator = createValidator(schema);
113
129
  const buildURL = createBuildURL(collectionName);
114
130
  const req = (...args) => createClient(baseUrl, state.authUser)(...args);
115
- const create = q => req(buildURL(), {
131
+ const create = ({ data, ...q }) => req(buildURL(null, q), {
116
132
  method: 'POST',
117
- body: Object.assign(q, { data: serializeData(q.data, schema) }),
118
- });
119
-
120
- const put = q => req(buildURL(q.where[primaryKey]), {
121
- method: 'PUT',
122
- body: Object.assign(q, { data: serializeData(q.data, schema) }),
133
+ body: serializeData(data, schema),
123
134
  });
135
+ const put = ({ data, ...q }) => {
136
+ const { where, ...rest } = q;
137
+ return req(buildURL(where[primaryKey], rest), {
138
+ method: 'PUT',
139
+ body: serializeData(data, schema),
140
+ });
141
+ };
124
142
 
125
143
  const parse = data => {
144
+ if (!data) {
145
+ return data;
146
+ }
147
+
126
148
  const parsed = Object.keys(data).reduce(
127
149
  (memo, key) => {
128
- const { type, format } = schema.properties[key] || {};
150
+ const propSchema = schema.properties[key] || {};
151
+ const { type, format, items } = propSchema;
152
+ if (items && isOneToManyRelation(schema, key)) {
153
+ const relationModelName = items.$ref.split('/').pop().toLowerCase();
154
+ return {
155
+ ...memo,
156
+ [key]: (
157
+ typeof models[relationModelName]?.parse === 'function'
158
+ ? data[key].map(obj => models[relationModelName].parse(obj))
159
+ : data[key]
160
+ ),
161
+ };
162
+ }
163
+ const ref = (
164
+ isRequiredOneToOneRelation(schema, key)
165
+ ? propSchema.$ref
166
+ : isOptionalOneToOneRelation(schema, key)
167
+ ? propSchema.anyOf[0]?.$ref
168
+ : null
169
+ );
170
+
171
+ if (ref) {
172
+ const relationModelName = ref.split('/').pop().toLowerCase();
173
+ return {
174
+ ...memo,
175
+ [key]: models[relationModelName].parse(data[key]),
176
+ };
177
+ }
129
178
  return {
130
179
  ...memo,
131
180
  [key]: (
@@ -144,16 +193,50 @@ export const createModel = (baseUrl, state, collectionName, schema = {}) => {
144
193
  );
145
194
  };
146
195
 
196
+ const relations = Object.keys(schema.properties || {}).reduce(
197
+ (memo, key) => (
198
+ isRequiredOneToOneRelation(schema, key)
199
+ ? Object.assign(memo, {
200
+ all: memo.all.concat(key),
201
+ oneToOne: memo.oneToOne.concat(key),
202
+ requiredOneToOne: memo.requiredOneToOne.concat(key),
203
+ })
204
+ : isOptionalOneToOneRelation(schema, key)
205
+ ? Object.assign(memo, {
206
+ all: memo.all.concat(key),
207
+ oneToOne: memo.oneToOne.concat(key),
208
+ optionalOneToOne: memo.optionalOneToOne.concat(key),
209
+ })
210
+ : isOneToManyRelation(schema, key)
211
+ ? Object.assign(memo, {
212
+ all: memo.all.concat(key),
213
+ oneToMany: memo.oneToMany.concat(key),
214
+ })
215
+ : memo
216
+ ),
217
+ {
218
+ all: [],
219
+ oneToOne: [],
220
+ requiredOneToOne: [],
221
+ optionalOneToOne: [],
222
+ oneToMany: [],
223
+ },
224
+ );
225
+
147
226
  return {
148
227
  get schema() {
149
- return schema;
228
+ return { ...schema, primaryKey };
150
229
  },
230
+ get relations() {
231
+ return relations;
232
+ },
233
+ parse,
151
234
  validateAttr: validator.validateAttr,
152
235
  validate: validator.validate,
153
236
  findMany: q => req(buildURL(null, q))
154
237
  .then(results => results.map(parse)),
155
238
  findById: (id, q) => req(buildURL(id, q))
156
- .then(raw => !!raw ? parse(raw) : raw),
239
+ .then(parse),
157
240
  create,
158
241
  update: put,
159
242
  upsert: opts => (
@@ -162,27 +245,30 @@ export const createModel = (baseUrl, state, collectionName, schema = {}) => {
162
245
  : put({ where: opts.where, data: opts.update })
163
246
  ),
164
247
  delete: id => req(buildURL(id), { method: 'DELETE' }),
248
+ stats: () => req(buildURL('_stats')),
165
249
  };
166
250
  };
167
251
 
252
+
168
253
  export const createModels = (url, state, schema) => {
169
254
  return Object.keys(schema.properties).reduce(
170
255
  (prev, key) => {
171
256
  const name = schema.properties[key].$ref.split('/').pop();
172
- return {
173
- ...prev,
257
+ return Object.assign(prev, {
174
258
  [key]: createModel(
175
259
  url,
176
260
  state,
177
261
  schema.definitions[name].plural || `${key}s`,
178
262
  schema.definitions[name],
263
+ prev,
179
264
  ),
180
- };
265
+ });
181
266
  },
182
267
  {},
183
268
  );
184
269
  };
185
270
 
271
+
186
272
  export const extendSchemaDefinitions = (schema, extended) => {
187
273
  return Object.keys(schema.definitions).reduce(
188
274
  (memo, key) => ({
@@ -208,4 +294,4 @@ export const extendSchemaDefinitions = (schema, extended) => {
208
294
  }),
209
295
  {},
210
296
  );
211
- };
297
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laboratoria/sdk-js",
3
- "version": "1.0.0",
3
+ "version": "1.4.0",
4
4
  "description": "Laboratoria JavaScript (browser) SDK",
5
5
  "scripts": {
6
6
  "test": "jest --verbose --coverage",
@@ -9,25 +9,25 @@
9
9
  "license": "MIT",
10
10
  "dependencies": {
11
11
  "blueimp-md5": "^2.19.0",
12
- "firebase": "^9.6.4"
12
+ "firebase": "^9.6.6"
13
13
  },
14
14
  "devDependencies": {
15
- "@babel/core": "^7.16.12",
15
+ "@babel/core": "^7.17.4",
16
16
  "@babel/plugin-transform-modules-commonjs": "^7.16.8",
17
- "babel-jest": "^27.4.6",
18
- "jest": "^27.4.7",
19
- "webpack": "^5.67.0",
17
+ "babel-jest": "^27.5.1",
18
+ "jest": "^27.5.1",
19
+ "webpack": "^5.69.0",
20
20
  "webpack-cli": "^4.9.2"
21
21
  },
22
22
  "jest": {
23
23
  "testEnvironment": "jsdom",
24
24
  "coverageThreshold": {
25
25
  "global": {
26
- "statements": 96,
27
- "branches": 91,
26
+ "statements": 96.9,
27
+ "branches": 94,
28
28
  "functions": 98,
29
- "lines": 96
29
+ "lines": 96.7
30
30
  }
31
31
  }
32
32
  }
33
- }
33
+ }
package/schemas/core.json CHANGED
@@ -123,6 +123,12 @@
123
123
  "$ref": "#/definitions/Contract"
124
124
  }
125
125
  },
126
+ "contractsAsCheckPilot": {
127
+ "type": "array",
128
+ "items": {
129
+ "$ref": "#/definitions/Contract"
130
+ }
131
+ },
126
132
  "gigs": {
127
133
  "type": "array",
128
134
  "items": {
@@ -485,30 +491,40 @@
485
491
  "isEmployment": {
486
492
  "type": "boolean"
487
493
  },
488
- "currency": {
489
- "type": "string",
490
- "enum": [
491
- "BRL",
492
- "CLP",
493
- "COP",
494
- "MXN",
495
- "PEN",
496
- "USD"
497
- ]
494
+ "feeAmount": {
495
+ "type": "integer"
498
496
  },
499
- "feeBasis": {
500
- "type": "string",
497
+ "isTrial": {
498
+ "type": "boolean",
499
+ "default": false
500
+ },
501
+ "source": {
502
+ "type": [
503
+ "string",
504
+ "null"
505
+ ],
501
506
  "enum": [
502
- "HOURLY",
503
- "MONTHLY"
507
+ "graduate",
508
+ "referral",
509
+ "jazzhr",
510
+ "web",
511
+ "linkedin",
512
+ "other"
504
513
  ]
505
514
  },
506
- "feeAmount": {
507
- "type": "integer"
508
- },
509
515
  "user": {
510
516
  "$ref": "#/definitions/User"
511
517
  },
518
+ "checkPilot": {
519
+ "anyOf": [
520
+ {
521
+ "$ref": "#/definitions/User"
522
+ },
523
+ {
524
+ "type": "null"
525
+ }
526
+ ]
527
+ },
512
528
  "country": {
513
529
  "$ref": "#/definitions/Country"
514
530
  },
@@ -1,31 +0,0 @@
1
- # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
-
4
- name: Node.js CI
5
-
6
- on:
7
- push:
8
- branches: [ main ]
9
- pull_request:
10
- branches: [ main ]
11
-
12
- jobs:
13
- build:
14
-
15
- runs-on: ubuntu-latest
16
-
17
- strategy:
18
- matrix:
19
- node-version: [14.x, 16.x]
20
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
-
22
- steps:
23
- - uses: actions/checkout@v2
24
- - name: Use Node.js ${{ matrix.node-version }}
25
- uses: actions/setup-node@v2
26
- with:
27
- node-version: ${{ matrix.node-version }}
28
- cache: 'npm'
29
- - run: npm ci
30
- - run: npm run build --if-present
31
- - run: npm test
package/index.spec.js DELETED
@@ -1,112 +0,0 @@
1
- import { initializeApp } from 'firebase/app';
2
- import {
3
- getAuth,
4
- onAuthStateChanged,
5
- signInWithEmailAndPassword,
6
- signOut,
7
- } from 'firebase/auth';
8
- import { createAPI as createCoreAPI } from './lib/core';
9
- import { createApp } from './index.js';
10
-
11
- jest.mock('./lib/core.js', () => ({
12
- createAPI: jest.fn().mockReturnValue({
13
- user: {
14
- findById: jest.fn().mockResolvedValue({}),
15
- },
16
- }),
17
- }));
18
-
19
- beforeEach(() => {
20
- initializeApp.mockClear();
21
- onAuthStateChanged.mockClear();
22
- signInWithEmailAndPassword.mockClear();
23
- });
24
-
25
- describe('createApp', () => {
26
- it('should invoke firebase\'s initializaApp', () => {
27
- createApp();
28
- expect(initializeApp).toHaveBeenCalledTimes(1);
29
- expect(initializeApp.mock.calls[0]).toMatchSnapshot();
30
- });
31
- });
32
-
33
- describe('app.auth.onChange', () => {
34
- it('should listen to firebase\'s onAuthStateChanged and notify subscribers when not authenticated', (done) => {
35
- const { auth } = createApp();
36
-
37
- onAuthStateChanged.mockImplementationOnce((_, cb) => {
38
- cb();
39
- return () => { };
40
- });
41
-
42
- auth.onChange(({ authUser, user }) => {
43
- expect(authUser).toBeNull();
44
- expect(user).toBeNull();
45
- done();
46
- });
47
- });
48
-
49
- it('should fetch user from db when authenticated using coreAPI', (done) => {
50
- const { auth } = createApp();
51
- const mockAuthUser = { uid: 'xxx', getIdToken: () => 'token' };
52
- const userMock = { uid: 'xxx', email: 'foo@bar.baz' };
53
-
54
- onAuthStateChanged.mockImplementationOnce((_, cb) => {
55
- cb(mockAuthUser);
56
- return () => { };
57
- });
58
-
59
- createCoreAPI().user.findById.mockResolvedValue(userMock);
60
-
61
- auth.onChange(({ authUser, user }) => {
62
- expect(authUser).toEqual(mockAuthUser);
63
- expect(user).toEqual(userMock);
64
- done();
65
- });
66
- });
67
-
68
- it('should log error and reset state (authUser = null, user = null) when fetch user from db fails', (done) => {
69
- const { auth } = createApp();
70
- const mockAuthUser = { uid: 'xxx', getIdToken: () => 'token' };
71
- const spy = jest.spyOn(console, 'error').mockImplementationOnce(() => { })
72
- const error = new Error('OMG');
73
-
74
- onAuthStateChanged.mockImplementationOnce((_, cb) => {
75
- cb(mockAuthUser);
76
- return () => { };
77
- });
78
-
79
- createCoreAPI().user.findById.mockRejectedValueOnce(error);
80
-
81
- auth.onChange(({ authUser, user }) => {
82
- expect(authUser).toBeNull();
83
- expect(user).toBeNull();
84
- expect(spy).toHaveBeenCalledWith(error);
85
- spy.mockRestore();
86
- done();
87
- });
88
- });
89
- });
90
-
91
- describe('app.auth.signIn', () => {
92
- it('should delegate to firebase\'s signInWithEmailAndPassword', async () => {
93
- const { auth } = createApp();
94
- const email = 'foo@bar.baz';
95
- const pass = 'secret';
96
-
97
- await auth.signIn(email, pass);
98
-
99
- expect(signInWithEmailAndPassword).toHaveBeenCalledTimes(1);
100
- expect(signInWithEmailAndPassword).toHaveBeenCalledWith({}, email, pass);
101
- });
102
- });
103
-
104
- describe('app.auth.signOut', () => {
105
- it('should delegate to firebase\'s signOut', async () => {
106
- const { auth } = createApp();
107
-
108
- await auth.signOut();
109
-
110
- expect(signOut).toHaveBeenCalledTimes(1);
111
- });
112
- });
@@ -1,77 +0,0 @@
1
- import { createClient } from './client.js';
2
-
3
- describe('client', () => {
4
-
5
- beforeAll(() => window.fetch = jest.fn());
6
-
7
- afterEach(() => {
8
- window.fetch.mockClear();
9
- jest.restoreAllMocks();
10
- });
11
-
12
- it('should delegate to global fetch and add mode:cors and parse JSON resp when OK', async () => {
13
- window.fetch.mockResolvedValue({
14
- json: jest.fn().mockResolvedValue({ ok: true }),
15
- });
16
- const baseUrl = 'http://foo.bar';
17
- const client = createClient(baseUrl);
18
- expect(await client('/')).toEqual({ ok: true });
19
- expect(window.fetch).toHaveBeenCalledTimes(1);
20
- expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
21
- mode: 'cors',
22
- headers: {},
23
- });
24
- });
25
-
26
- it('should reject when response.status > 202', async () => {
27
- window.fetch.mockResolvedValue({
28
- status: 400,
29
- json: jest.fn().mockResolvedValue({ ok: false }),
30
- });
31
- const baseUrl = 'http://foo.bar';
32
- const client = createClient(baseUrl);
33
- await expect(client('/')).rejects.toThrow('HTTP Error 400');
34
- expect(window.fetch).toHaveBeenCalledTimes(1);
35
- expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
36
- mode: 'cors',
37
- headers: {},
38
- });
39
- });
40
-
41
- it('should add token when authUser present', async () => {
42
- window.fetch.mockResolvedValue({
43
- json: jest.fn().mockResolvedValue({ ok: true }),
44
- });
45
- const baseUrl = 'http://foo.bar';
46
- const user = { getIdToken: jest.fn().mockReturnValueOnce('xxx') };
47
- const client = createClient(baseUrl, user);
48
- expect(await client('/')).toEqual({ ok: true });
49
- expect(window.fetch).toHaveBeenCalledTimes(1);
50
- expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
51
- mode: 'cors',
52
- headers: {
53
- authorization: 'Bearer xxx',
54
- },
55
- });
56
- expect(user.getIdToken).toHaveBeenCalledTimes(1);
57
- });
58
-
59
- it('should add content-type header and stringify body when necessary', async () => {
60
- window.fetch.mockResolvedValue({
61
- json: jest.fn().mockResolvedValue({ ok: true }),
62
- });
63
- const baseUrl = 'http://foo.bar';
64
- const client = createClient(baseUrl);
65
- const body = { foo: 'bar' };
66
- expect(await client('/', { method: 'POST', body })).toEqual({ ok: true });
67
- expect(window.fetch).toHaveBeenCalledTimes(1);
68
- expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
69
- mode: 'cors',
70
- headers: {
71
- 'content-type': 'application/json',
72
- },
73
- method: 'POST',
74
- body: JSON.stringify(body),
75
- });
76
- });
77
- });
package/lib/currencies.js DELETED
@@ -1,3 +0,0 @@
1
- const currencies = ['BRL', 'CLP', 'COP', 'MXN', 'PEN', 'USD'];
2
-
3
- export default currencies;
@@ -1,194 +0,0 @@
1
- import { createAPI } from './curriculum';
2
-
3
- describe('projects', () => {
4
- describe('findMany', () => {
5
- // TODO: errores HTTP, GitHub caido, repo borrado, etc...
6
-
7
- it('sends request to get latest version and then corresponding json', () => {
8
- global.fetch = jest.fn().mockImplementation(async url => ({
9
- status: 200,
10
- json: jest.fn().mockResolvedValue(
11
- url.startsWith('https://api.')
12
- ? { tag_name: 'v4.0.0' }
13
- : []
14
- ),
15
- }));
16
-
17
- return createAPI().projects.findMany()
18
- .then((projects) => {
19
- expect(projects).toEqual([]);
20
- expect(global.fetch).toHaveBeenCalledTimes(2);
21
- expect(global.fetch).toHaveBeenNthCalledWith(
22
- 1,
23
- 'https://api.github.com/repos/Laboratoria/bootcamp/releases/latest',
24
- );
25
- expect(global.fetch).toHaveBeenNthCalledWith(
26
- 2,
27
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v4.0.0/dist/projects.json',
28
- );
29
- });
30
- });
31
-
32
- it('gets projects in specific tag when passed as option', () => {
33
- global.fetch = jest.fn().mockImplementation(async url => ({
34
- status: 200,
35
- json: jest.fn().mockResolvedValue(
36
- url.startsWith('https://api.')
37
- ? { tag_name: 'v4.0.0' }
38
- : []
39
- ),
40
- }));
41
-
42
- return createAPI().projects.findMany({ tag: 'v3.0.0' })
43
- .then((projects) => {
44
- expect(projects).toEqual([]);
45
- expect(global.fetch).toHaveBeenCalledTimes(1);
46
- expect(global.fetch).toHaveBeenCalledWith(
47
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v3.0.0/dist/projects.json',
48
- );
49
- });
50
- });
51
-
52
- it('gets projects in specific tag when passed as option', () => {
53
- global.fetch = jest.fn().mockImplementation(async url => ({
54
- status: 200,
55
- json: jest.fn().mockResolvedValue(
56
- url.startsWith('https://api.')
57
- ? { tag_name: 'v4.0.0' }
58
- : [
59
- { locale: 'es-ES', track: 'ux' },
60
- { locale: 'es-ES', track: 'js' },
61
- { locale: 'pt-BR', track: 'ux' },
62
- ]
63
- ),
64
- }));
65
-
66
- return createAPI().projects.findMany({
67
- tag: 'v3.0.0',
68
- locale: 'es-ES',
69
- track: 'ux',
70
- })
71
- .then((projects) => {
72
- expect(projects).toEqual([{ locale: 'es-ES', track: 'ux' }]);
73
- expect(global.fetch).toHaveBeenCalledTimes(1);
74
- expect(global.fetch).toHaveBeenCalledWith(
75
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v3.0.0/dist/projects.json',
76
- );
77
- });
78
- });
79
-
80
- // it should filter by tag, locale and track...
81
- });
82
-
83
- describe('findById', () => {
84
- it('sends request to get latest version and then corresponding json', () => {
85
- global.fetch = jest.fn().mockImplementation(async url => ({
86
- status: 200,
87
- json: jest.fn().mockResolvedValue(
88
- url.startsWith('https://api.')
89
- ? { tag_name: 'v4.0.0' }
90
- : {}
91
- ),
92
- }));
93
-
94
- return createAPI().projects.findById('cipher')
95
- .then((project) => {
96
- expect(project).toEqual({});
97
- expect(global.fetch).toHaveBeenCalledTimes(2);
98
- expect(global.fetch).toHaveBeenNthCalledWith(
99
- 1,
100
- 'https://api.github.com/repos/Laboratoria/bootcamp/releases/latest',
101
- );
102
- expect(global.fetch).toHaveBeenNthCalledWith(
103
- 2,
104
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v4.0.0/dist/projects/cipher.json',
105
- );
106
- });
107
- });
108
- });
109
- });
110
-
111
- describe('topics', () => {
112
- describe('findMany', () => {
113
- it('sends request to get latest version and then corresponding json', () => {
114
- global.fetch = jest.fn().mockImplementation(async url => ({
115
- status: 200,
116
- json: jest.fn().mockResolvedValue(
117
- url.startsWith('https://api.')
118
- ? { tag_name: 'v4.0.0' }
119
- : []
120
- ),
121
- }));
122
-
123
- return createAPI().topics.findMany()
124
- .then((topics) => {
125
- expect(topics).toEqual([]);
126
- expect(global.fetch).toHaveBeenCalledTimes(2);
127
- expect(global.fetch).toHaveBeenNthCalledWith(
128
- 1,
129
- 'https://api.github.com/repos/Laboratoria/bootcamp/releases/latest',
130
- );
131
- expect(global.fetch).toHaveBeenNthCalledWith(
132
- 2,
133
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v4.0.0/dist/topics.json',
134
- );
135
- });
136
- });
137
- });
138
-
139
- describe('findById', () => {
140
- it('sends request to get latest version and then corresponding json', () => {
141
- global.fetch = jest.fn().mockImplementation(async url => ({
142
- status: 200,
143
- json: jest.fn().mockResolvedValue(
144
- url.startsWith('https://api.')
145
- ? { tag_name: 'v4.0.0' }
146
- : {}
147
- ),
148
- }));
149
-
150
- return createAPI().topics.findById('javascript')
151
- .then((topic) => {
152
- expect(topic).toEqual({});
153
- expect(global.fetch).toHaveBeenCalledTimes(2);
154
- expect(global.fetch).toHaveBeenNthCalledWith(
155
- 1,
156
- 'https://api.github.com/repos/Laboratoria/bootcamp/releases/latest',
157
- );
158
- expect(global.fetch).toHaveBeenNthCalledWith(
159
- 2,
160
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v4.0.0/dist/topics/javascript.json',
161
- );
162
- });
163
- });
164
- });
165
- });
166
-
167
- describe('learningObjectives', () => {
168
- describe('findMany', () => {
169
- it('sends request to get latest version and then corresponding json', () => {
170
- global.fetch = jest.fn().mockImplementation(async url => ({
171
- status: 200,
172
- json: jest.fn().mockResolvedValue(
173
- url.startsWith('https://api.')
174
- ? { tag_name: 'v4.0.0' }
175
- : []
176
- ),
177
- }));
178
-
179
- return createAPI().learningObjectives.findMany()
180
- .then((learningObjectives) => {
181
- expect(learningObjectives).toEqual([]);
182
- expect(global.fetch).toHaveBeenCalledTimes(2);
183
- expect(global.fetch).toHaveBeenNthCalledWith(
184
- 1,
185
- 'https://api.github.com/repos/Laboratoria/bootcamp/releases/latest',
186
- );
187
- expect(global.fetch).toHaveBeenNthCalledWith(
188
- 2,
189
- 'https://raw.githubusercontent.com/Laboratoria/bootcamp/v4.0.0/dist/learning-objectives.json',
190
- );
191
- });
192
- });
193
- });
194
- });
package/lib/model.spec.js DELETED
@@ -1,242 +0,0 @@
1
- import { createModel } from './model.js';
2
- import { createClient } from './client.js';
3
-
4
- jest.mock('./client.js');
5
-
6
- beforeEach(() => createClient().mockClear());
7
-
8
- describe('model.schema', () => {
9
- it('should be empty object by default', () => {
10
- const model = createModel('http://1.2.3.4', {}, 'foo');
11
- expect(model.schema).toEqual({});
12
- });
13
-
14
- it('should get the model\'s schema', () => {
15
- const schema = {
16
- properties: {
17
- a: { type: 'string' },
18
- },
19
- };
20
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
21
- expect(model.schema).toEqual(schema);
22
- });
23
- });
24
-
25
- describe('model.findMany(options)', () => {
26
- it('should send GET request', async () => {
27
- const schema = {
28
- properties: {
29
- id: { type: 'integer' },
30
- createdAt: { type: 'string', format: 'date-time' },
31
- },
32
- };
33
- const now = new Date();
34
- const client = createClient().mockResolvedValue([{
35
- id: 1,
36
- createdAt: now.toISOString(),
37
- }]);
38
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
39
- const results = await model.findMany();
40
- expect(results).toEqual([{ id: 1, createdAt: now }]);
41
- expect(results[0].createdAt instanceof Date).toBe(true);
42
- expect(client).toHaveBeenCalledTimes(1);
43
- expect(client).toHaveBeenCalledWith('/foo');
44
- });
45
- });
46
-
47
- describe('model.findById(id, options)', () => {
48
- it('should send GET request', async () => {
49
- const client = createClient().mockResolvedValue({});
50
- const model = createModel('http://1.2.3.4', {}, 'foo');
51
- expect(await model.findById(1)).toEqual({});
52
- expect(client).toHaveBeenCalledTimes(1);
53
- expect(client).toHaveBeenCalledWith('/foo/1');
54
- });
55
- });
56
-
57
- describe('model.create(options)', () => {
58
- it('should send POST request', async () => {
59
- const client = createClient().mockResolvedValue({});
60
- const model = createModel('http://1.2.3.4', {}, 'foo');
61
- expect(await model.create({ data: { ok: true } })).toEqual({});
62
- expect(client).toHaveBeenCalledTimes(1);
63
- expect(client).toHaveBeenCalledWith('/foo', {
64
- body: { data: { ok: true } },
65
- method: 'POST',
66
- });
67
- });
68
- });
69
-
70
- describe('model.update(options)', () => {
71
- it('should send POST request', async () => {
72
- const client = createClient().mockResolvedValue({});
73
- const model = createModel('http://1.2.3.4', {}, 'foo');
74
- expect(await model.update({ where: { id: 1 }, data: { ok: true } })).toEqual({});
75
- expect(client).toHaveBeenCalledTimes(1);
76
- expect(client).toHaveBeenCalledWith('/foo/1', {
77
- body: { where: { id: 1 }, data: { ok: true } },
78
- method: 'PUT',
79
- });
80
- });
81
-
82
- it('should exclude null values for optional relation props', async () => {
83
- const client = createClient().mockResolvedValue({});
84
- const schema = {
85
- properties: {
86
- signupCohort: {
87
- anyOf: [
88
- { '$ref': '#/definitions/Cohort' },
89
- { type: 'null' },
90
- ],
91
- },
92
- },
93
- };
94
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
95
- expect(await model.update({
96
- where: { id: 1 },
97
- data: { signupCohort: null },
98
- })).toEqual({});
99
- expect(client).toHaveBeenCalledTimes(1);
100
- expect(client).toHaveBeenCalledWith('/foo/1', {
101
- body: {
102
- where: { id: 1 },
103
- data: {},
104
- },
105
- method: 'PUT',
106
- });
107
- });
108
- });
109
-
110
- describe('model.upsert(options)', () => {
111
- it('should send POST request when new row', async () => {
112
- const client = createClient().mockResolvedValue({});
113
- const model = createModel('http://1.2.3.4', {}, 'foo');
114
- const result = await model.upsert({
115
- where: {},
116
- create: { ok: true },
117
- });
118
- expect(result).toEqual({});
119
- expect(client).toHaveBeenCalledTimes(1);
120
- expect(client).toHaveBeenCalledWith('/foo', {
121
- body: { data: { ok: true } },
122
- method: 'POST',
123
- });
124
- });
125
-
126
- it('should send PUT request when existing row', async () => {
127
- const client = createClient().mockResolvedValue({});
128
- const model = createModel('http://1.2.3.4', {}, 'foo');
129
- const result = await model.upsert({
130
- where: { id: 1 },
131
- update: { ok: true },
132
- });
133
- expect(result).toEqual({});
134
- expect(client).toHaveBeenCalledTimes(1);
135
- expect(client).toHaveBeenCalledWith('/foo/1', {
136
- body: {
137
- where: { id: 1 },
138
- data: { ok: true },
139
- },
140
- method: 'PUT',
141
- });
142
- });
143
- });
144
-
145
- describe('model.delete(id)', () => {
146
- it('should send POST request', async () => {
147
- const client = createClient().mockResolvedValue({});
148
- const model = createModel('http://1.2.3.4', {}, 'foo');
149
- expect(await model.delete(1)).toEqual({});
150
- expect(client).toHaveBeenCalledTimes(1);
151
- expect(client).toHaveBeenCalledWith('/foo/1', {
152
- method: 'DELETE',
153
- });
154
- });
155
- });
156
-
157
- describe('model.validateAttr(key, value)', () => {
158
- it('should not validate when no schema', () => {
159
- const model = createModel('http://1.2.3.4', {}, 'foo');
160
- expect(model.validateAttr('a', 1)).toBeUndefined();
161
- });
162
-
163
- it('should validate text when required', () => {
164
- const schema = {
165
- properties: {
166
- a: { type: 'string' },
167
- },
168
- };
169
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
170
- expect(model.validateAttr('a', '')).toBe('Field is required');
171
- });
172
-
173
- it('should validate number when required', () => {
174
- const schema = {
175
- properties: {
176
- a: { type: 'integer', required: true },
177
- },
178
- };
179
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
180
- expect(model.validateAttr('a', 'foo')).toBe('Invalid number format');
181
- });
182
-
183
- it('should validate enum', () => {
184
- const schema = {
185
- properties: {
186
- a: { type: 'integer', enum: [1, 2, 3] },
187
- },
188
- };
189
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
190
- expect(model.validateAttr('a', 'foo')).toBe('foo is not one of: 1,2,3');
191
- });
192
-
193
- it('should allow empty enum when not required', () => {
194
- const schema = {
195
- properties: {
196
- a: { type: ['integer', 'null'], enum: [1, 2, 3] },
197
- },
198
- };
199
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
200
- expect(model.validateAttr('a', null)).toBeUndefined();
201
- expect(model.validateAttr('a', undefined)).toBeUndefined();
202
- expect(model.validateAttr('a', '')).toBeUndefined();
203
- });
204
- });
205
-
206
- describe('model.validate(attributes)', () => {
207
- it('should not validate when no schema', () => {
208
- const model = createModel('http://1.2.3.4', {}, 'foo');
209
- expect(model.validate({ a: 1 })).toEqual({});
210
- });
211
-
212
- it('should validate when schema present', () => {
213
- const schema = {
214
- properties: {
215
- a: { type: 'string' },
216
- b: { type: 'integer' },
217
- },
218
- };
219
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
220
- expect(model.validate({ a: '', b: 1 })).toEqual({ a: 'Field is required' });
221
- });
222
-
223
- it('should validate number when required', () => {
224
- const schema = {
225
- properties: {
226
- a: { type: 'integer' },
227
- },
228
- };
229
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
230
- expect(model.validateAttr('a', 'foo')).toBe('Invalid number format');
231
- });
232
-
233
- it('should validate enum', () => {
234
- const schema = {
235
- properties: {
236
- a: { type: 'integer', enum: [1, 2, 3] },
237
- },
238
- };
239
- const model = createModel('http://1.2.3.4', {}, 'foo', schema);
240
- expect(model.validateAttr('a', 'foo')).toBe('foo is not one of: 1,2,3');
241
- });
242
- });