-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
211 lines (170 loc) · 5.97 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
const SchemaFactory = require('sequelize-to-json-schema');
const _ = require('lodash');
const chai = require('chai');
// eslint-disable-next-line import/no-extraneous-dependencies
const shallowDeepEqual = require('chai-shallow-deep-equal');
chai.use(shallowDeepEqual);
const { SequelizeSchema } = SchemaFactory;
const expect = chai.expect;
/**
* Helper for verifying that schema contains what we want
*/
class SchemaHelper {
/**
* Constructs a helper for the given sequelize model class
* @param {Model} model The sequelize model this helper is for
*/
constructor(model, options = {}) {
if (!model || !model.tableName) {
throw new Error(`Are you sure model is a sequelize model class? ${model}`);
}
this.model = model;
this.customSchema = options.customSchema || {};
this.options = options;
this.schema = new SequelizeSchema(model, options);
}
/**
* Get the model attribute name, calls provided helper if available
* to do custom mappings
*
* @param {String} key The json schema key
* @returns {String} The corresponding model attribute name
* @throws {Error} if no corresponding model attribute can be found
*/
getModelAttribute(key) {
let modelKey = key;
if (this.isVirtualProperty(key)) {
return key;
}
if (this.options.modelAttributeMapper) {
modelKey = this.options.modelAttributeMapper(this.model, key);
}
if (!this.model.attributes[modelKey]) {
throw new Error(`Unknown attribute ${modelKey} (for model ${this.model.name})`);
}
return modelKey;
}
/**
* Asserts that the schema documents all the fields of a given example
*
* This will take an example object and compare the schema against it
* @param {Object} response A HTTP response object (must contain .body)
* @param {Object} example An example object to compare against
*/
assertAllExampleFields(response, example) {
let privateSchema = {};
Object.keys(example).forEach((key) => {
const modelKey = this.getModelAttribute(key);
const isVirtual = this.isVirtualProperty(key);
const type = isVirtual ? this.virtualPropertyType(key) :
this.schema.getDbType(modelKey);
const property = this.schema.generatePropertySchema({
key,
type,
});
// If it's an enum type, list the allowed values
if (!isVirtual && (this.schema.getDbType(modelKey) === 'ENUM')) {
property.enum = this.model.attributes[modelKey].type.values;
}
if (this.customSchema[this.model.name]
&& this.customSchema[this.model.name][key]) {
Object.assign(property, this.customSchema[this.model.name][key]);
}
privateSchema[key] = property;
});
privateSchema = _.fromPairs(_.toPairs(privateSchema).sort((a, b) => a[0].localeCompare(b[0])));
expect(response.body.properties).to.shallowDeepEqual(privateSchema);
}
isVirtualProperty(key) {
return this.options.virtualProperties &&
this.options.virtualProperties[this.model.name] &&
this.options.virtualProperties[this.model.name][key];
}
virtualPropertyType(key) {
return this.options.virtualProperties[this.model.name][key].type;
}
/**
* Asserts that the json schema documens all associations
* @param {Object} response a http response object, must contain .body
*/
assertAssociations(response, associations) {
if (!associations) {
throw new Error('Please specify the associations you want to validate the presence of');
}
const expectedProperties = this.schema.getProperties(associations);
expect(response.body.properties).to.shallowDeepEqual(expectedProperties);
}
assertProperties(response, attributes) {
const props = {};
attributes.forEach((attr) => {
const jsonKey = this.schema.getJsonAttribute(attr);
props[jsonKey] = {
$id: `/properties/${jsonKey}`,
};
});
expect(response.body.properties).to.shallowDeepEqual(props);
}
boilerPlate() {
return this.schema.boilerPlate();
}
/**
* Asserts that all (top level) properties are described fully
*
* If fields is omitted, the default is ['description', 'examples', 'type', 'title', '$id']
*
* @param {HttpResponse} response The response object (must contain body)
* @param {String[]} fields optional list of attributes that each property must have
*/
assertAllPropertiesDescribed(response, fields) {
// eslint-disable-next-line no-param-reassign
if (!fields) fields = ['description', 'examples', 'type', 'title', '$id'];
const missing = this.checkProperties(response.body, fields, this.model);
if (Object.keys(missing).length) {
expect({}, 'Properties are not fully described, see diff for missing properties').to.equal(missing);
}
}
// eslint-disable-next-line class-methods-use-this
checkProperties(schema, fields, model) {
const missing = {};
_.forEach(schema.properties, (prop, key) => {
let fieldsNeeded = fields;
if (prop.type === 'array' && model.associations[key]) {
fieldsNeeded = ['$id', 'title', 'items'];
const missingKeys = this.checkProperties(prop.items,
fields, model.associations[key].target);
if (Object.keys(missingKeys).length) {
missing[key] = {
items: missingKeys,
};
}
}
const isReference = prop.$ref;
// (prop.type === 'object' && model.associations[key]);
if (prop.type === 'object' && model.associations[key]) {
fieldsNeeded = ['$id', 'title', 'properties'];
const missingKeys = this.checkProperties(prop,
fields, model.associations[key].target);
if (Object.keys(missingKeys).length) {
missing[key] = {
properties: missingKeys,
};
}
}
// Ignore associations
if (!isReference) {
const missingKeys = _.difference(fieldsNeeded, Object.keys(prop));
if (missingKeys.length) {
if (!missing[key]) missing[key] = {};
missing[key].requiredAttributes = missingKeys;
}
}
});
return missing;
}
static getTestHelper(model, options) {
const factory = new SchemaFactory(options);
const opts = Object.assign({ factory }, factory.options, options || {});
return new SchemaHelper(model, opts);
}
}
module.exports = SchemaHelper;