Merge pull request #1843 from xtuple/4_6_x
[xtuple] / test / lib / runner_engine.js
1 /*jshint trailing:true, white:true, indent:2, strict:true, curly:true,
2   immed:true, eqeqeq:true, forin:true, latedef:true,
3   newcap:true, noarg:true, undef:true */
4 /*global XT:true, XM:true, XV:true, exports:true, describe:true, it:true,
5 require:true, __dirname:true, console:true */
6
7 // TODO: test "used"
8 // i.e.: A XM.ShipVia object can not be deleted if it has been assigned as the default customer ship via in sales settings.
9
10 // TODO: test defaults
11
12 (function () {
13   "use strict";
14
15   var crud = require('./crud'),
16     smoke = require('./smoke'),
17     _ = require("underscore"),
18     path = require('path'),
19     assert = require("chai").assert,
20     zombieAuth = require("./zombie_auth");
21
22   var runSpec = function (specContents) {
23     if (!specContents) {
24       return;
25     }
26
27     var spec = specContents.spec;
28
29     describe(spec.recordType + " test", function () {
30       this.pending = spec.skipAll;
31
32       if (_.isString(spec.updatableField)) {
33         spec.updateHash = {};
34         spec.updateHash[spec.updatableField] = "Test" + Math.random();
35       }
36
37
38       if (spec.skipBoilerplateTests && specContents.additionalTests) {
39         specContents.additionalTests();
40         return;
41       } else if (spec.skipBoilerplateTests) {
42         return;
43       }
44
45       //
46       // Run CRUD model tests
47       //
48       if (!spec.skipCrud) {
49         crud.runAllCrud(spec);
50       } else {
51         // even if we skip CRUD we have to create a model
52         it('can be loaded with a zombie session', function (done) {
53           this.timeout(40 * 1000);
54           zombieAuth.loadApp({callback: done, verbose: spec.verbose,
55             loginDataPath: spec.loginDataPath});
56         });
57         it('can be created', function () {
58           spec.model = new XM[spec.recordType.substring(3)]();
59         });
60       }
61
62       //
63       // Smoke Crud
64       //
65       if (!spec.skipSmoke) {
66         smoke.runUICrud(spec);
67       }
68
69       if (!spec.skipModelConfig) {
70         //
71         // Verify required fields
72         //
73         if (spec.requiredAttributes) {
74           _.each(spec.requiredAttributes, function (attr) {
75             it("the " + attr + " attribute is required", function () {
76               assert.include(spec.model.requiredAttributes, attr);
77             });
78           });
79         }
80
81         //
82         // Verify lockability
83         //
84         it(spec.isLockable ? "is lockable" : "is not lockable", function () {
85           assert.equal(spec.isLockable, spec.model.lockable);
86         });
87
88         //
89         // Verify inheritance
90         //
91         if (spec.instanceOf === "XM.Document") {
92           it("inherits from XM.Document", function () {
93             assert.isTrue(spec.model instanceof XM.Document);
94           });
95         } else if (spec.instanceOf === "XM.Model") {
96           it("inherits from XM.Model but not XM.Document", function () {
97             assert.isTrue(spec.model instanceof XM.Model);
98             assert.isFalse(spec.model instanceof XM.Document);
99           });
100         } else {
101           it("has its inheritance defined in the test spec", function () {
102             assert.fail();
103           });
104         }
105
106         //
107         // Verify ID attribute
108         //
109         if (spec.idAttribute) {
110           it("has " + spec.idAttribute + " as its idAttribute", function () {
111             assert.equal(spec.idAttribute, spec.model.idAttribute);
112           });
113         } else {
114           it("has its id attribute defined in the test spec", function () {
115             assert.fail();
116           });
117         }
118
119         //
120         // Verify Document Key
121         //
122         if (spec.documentKey) {
123           it("has " + spec.documentKey + " as its documentKey", function () {
124             assert.equal(spec.documentKey, spec.model.documentKey);
125           });
126         }
127
128         //
129         // Make sure we're testing the enforceUpperCase (the asserts themselves are in CRUD)
130         //
131         if (spec.enforceUpperKey) {
132           it((spec.enforceUpperKey ? "Enforces" : "Does not enforce") + " uppercasing the key", function () {
133             assert.equal(spec.model.enforceUpperKey, spec.enforceUpperKey);
134           });
135           if (!_.isBoolean(spec.enforceUpperKey)) {
136             it("has its enforceUpperKey convention defined in the test spec", function () {
137               assert.fail();
138             });
139           }
140         }
141
142         //
143         // Verify attributes
144         //
145         _.each(spec.attributes, function (attr) {
146           it("contains the " + attr + " attribute", function () {
147             assert.include(spec.model.getAttributeNames(), attr);
148           });
149         });
150         if (!spec.attributes || spec.attributes.length === 0) {
151           it("has some attributes defined in the test spec", function () {
152             assert.fail();
153           });
154         }
155
156         //
157         // Verify privileges are declared correctly by the extensions
158         //
159         _.each(spec.privileges, function (priv) {
160           if (typeof priv === 'string') {
161             _.each(spec.extensions, function (extension) {
162               it("has privilege " + priv + " declared by the " + extension + " extension", function () {
163                 assert.isDefined(_.findWhere(XT.session.relevantPrivileges,
164                   {privilege: priv, module: spec.relevantPrivilegeModule || extension}),
165                   "Perhaps you're adding the privilege in a different module and haven't set the " +
166                   "spec.relevantPrivilegeModule attribute to be that module?");
167               });
168             });
169             /*
170             XXX this could get tripped up by non-core extensions
171             it("has privilege " + priv + " not declared by any other extensions", function () {
172               var matchedPriv = _.filter(XT.session.relevantPrivileges, function (sessionPriv) {
173                 return sessionPriv.privilege === priv && !_.contains(spec.extensions, sessionPriv.module);
174               });
175               assert.equal(0, matchedPriv.length);
176             });
177             */
178             //
179             // Make sure the privilege is translated
180             //
181             it("has privilege " + priv + " that is translated in the strings file", function () {
182               var privLoc = "_" + XT.String.camelize(priv);
183               assert.notEqual(XT.String.loc(privLoc), privLoc);
184             });
185           }
186         });
187
188         //
189         // Verify Privileges
190         //
191         _.each(spec.privileges, function (priv, key) {
192           var methodMap = {
193               createReadUpdateDelete: ["canCreate", "canRead", "canUpdate", "canDelete"],
194               createUpdateDelete: ["canCreate", "canUpdate", "canDelete"],
195               createUpdate: ["canCreate", "canUpdate"],
196               create: ["canCreate"],
197               read: ["canRead"],
198               update: ["canUpdate"],
199               delete: ["canDelete"]
200             },
201             pertinentMethods = methodMap[key],
202             updatePriv = spec.privileges.update ||
203               spec.privileges.createUpdate ||
204               spec.privileges.createUpdateDelete;
205
206           it("needs " + priv + " privilege to perform action " + key, function () {
207             var schema = XT.session.schemas.XM.get(XT.String.suffix(spec.recordType)),
208               personalPrivs = schema.privileges.personal,
209               Klass = XT.getObjectByName(spec.recordType);
210
211             if (personalPrivs) {
212               //console.log("skipping personal");
213               // TODO: don't let personal privs mess us up. Find a way to test them.
214             } else if (_.isString(priv)) {
215               assert.isDefined(pertinentMethods); // make sure we're testing for the priv
216               XT.session.privileges.attributes[priv] = false;
217               if (key === "read" && updatePriv) {
218                 // update privs are sufficient for read, so we have to toggle those too
219                 XT.session.privileges.attributes[updatePriv] = false;
220               }
221               _.each(pertinentMethods, function (pertinentMethod) {
222                 assert.isFalse(Klass[pertinentMethod]());
223               });
224               XT.session.privileges.attributes[priv] = true;
225               if (key === "read" && updatePriv) {
226                 // update privs are sufficient for read, so we have to toggle those too
227                 XT.session.privileges.attributes[updatePriv] = true;
228               }
229               _.each(pertinentMethods, function (pertinentMethod) {
230                 assert.isTrue(Klass[pertinentMethod]());
231               });
232
233             } else if (_.isBoolean(priv)) {
234               _.each(pertinentMethods, function (pertinentMethod) {
235                 assert.equal(Klass[pertinentMethod](), priv);
236               });
237
238             } else {
239               it("has privilege " + priv + " that's a string or boolean in the test spec", function () {
240                 assert.fail();
241               });
242             }
243           });
244         });
245
246         //
247         // Test that the collection exists
248         //
249         if (spec.collectionType) {
250           it("backs the " + spec.collectionType + " collection", function () {
251             var Collection = XT.getObjectByName(spec.collectionType),
252               modelPrototype = Collection.prototype.model.prototype,
253               editableModel = modelPrototype.editableModel || modelPrototype.recordType;
254
255             assert.isFunction(Collection);
256             assert.equal(editableModel, spec.recordType);
257           });
258         } else if (spec.collectionType === null) {
259           // TODO: loop through the existing collections and make sure that
260           // none are backed by spec.recordType
261         } else {
262           it("has no colletion specified in the test spec", function () {
263             assert.fail();
264           });
265         }
266
267         //
268         // Test that the cache exists
269         //
270         if (spec.cacheName) {
271           it("is cached as " + spec.cacheName, function () {
272             var cache = XT.getObjectByName(spec.cacheName);
273             assert.isObject(cache);
274             assert.equal(cache.model.prototype.recordType, spec.recordType);
275           });
276
277         } else if (spec.cacheName === null) {
278           /*
279           TODO: probably the best thing to do is to loop through the caches and make sure
280           that none of them are backed by spec.recordType
281           it("is not cached", function () {
282
283           });
284           */
285         } else {
286           it("has a cache (or null for no cache) specified in the test spec", function () {
287             assert.fail();
288           });
289         }
290       }
291
292       // TODO: verify that the cache is made available by certain extensions and not others
293       // TODO: verify that the list is made available by certain extensions and not others
294
295       if (specContents.additionalTests) {
296         specContents.additionalTests();
297       }
298       if (specContents.extensionTests) {
299         specContents.extensionTests();
300       }
301
302     });
303   };
304
305   exports.runSpec = runSpec;
306 }());