loopback-angular
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -18,3 +18,2 @@ // Karma configuration | ||
'test.e2e/test-main.js', | ||
{ pattern: 'node_modules/mocha-as-promised/**/*.js', included: false }, | ||
{ pattern: 'bower_components/**/*.js', included: false }, | ||
@@ -35,5 +34,10 @@ { pattern: 'test.e2e/**/*.js', included: false }, | ||
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' | ||
reporters: ['progress'], | ||
reporters: ['progress', 'junit'], | ||
// CI friendly test output | ||
junitReporter: { | ||
outputFile: 'karma-xunit.xml' | ||
}, | ||
// web server port | ||
@@ -40,0 +44,0 @@ port: 9876, |
@@ -38,5 +38,115 @@ var fs = require('fs'); | ||
// The URL of prototype methods include sharedCtor parameters like ":id" | ||
// Because all $resource methods are static (non-prototype) in ngResource, | ||
// the sharedCtor parameters should be added to the parameters | ||
// of prototype methods. | ||
c.methods.forEach(function fixArgsOfPrototypeMethods(method) { | ||
var ctor = method.restClass.ctor; | ||
if (!ctor || method.sharedMethod.isStatic) return; | ||
method.accepts = ctor.accepts.concat(method.accepts); | ||
}); | ||
result[name] = c; | ||
}); | ||
buildScopes(result); | ||
return result; | ||
} | ||
var SCOPE_METHOD_REGEX = /^prototype.__([^_]+)__(.+)$/; | ||
function buildScopes(models) { | ||
for (var modelName in models) { | ||
buildScopesOfModel(models, modelName); | ||
} | ||
} | ||
function buildScopesOfModel(models, modelName) { | ||
var modelClass = models[modelName]; | ||
modelClass.scopes = {}; | ||
modelClass.methods.forEach(function(method) { | ||
buildScopeMethod(models, modelName, method); | ||
}); | ||
return modelClass; | ||
} | ||
// reverse-engineer scope method | ||
// defined by loopback-datasource-juggler/lib/scope.js | ||
function buildScopeMethod(models, modelName, method) { | ||
var modelClass = models[modelName]; | ||
var match = method.name.match(SCOPE_METHOD_REGEX); | ||
if (!match) return; | ||
var op = match[1]; | ||
var scopeName = match[2]; | ||
var modelPrototype = modelClass.sharedClass.ctor.prototype; | ||
var targetClass = modelPrototype[scopeName]._targetClass; | ||
if (modelClass.scopes[scopeName] === undefined) { | ||
if (!targetClass) { | ||
console.error( | ||
'Warning: scope %s.%s is missing _targetClass property.' + | ||
'\nThe Angular code for this scope won\'t be generated.' + | ||
'\nPlease upgrade to the latest version of' + | ||
'\nloopback-datasource-juggler to fix the problem.', | ||
modelName, scopeName); | ||
modelClass.scopes[scopeName] = null; | ||
return; | ||
} | ||
if (!findModelByName(models, targetClass)) { | ||
console.error( | ||
'Warning: scope %s.%s targets class %j, which is not exposed ' + | ||
'\nvia remoting. The Angular code for this scope won\'t be generated.', | ||
modelName, scopeName, targetClass); | ||
modelClass.scopes[scopeName] = null; | ||
return; | ||
} | ||
modelClass.scopes[scopeName] = { | ||
methods: {}, | ||
targetClass: targetClass | ||
}; | ||
} else if (modelClass.scopes[scopeName] === null) { | ||
// Skip the scope, the warning was already reported | ||
return; | ||
} | ||
var apiName = scopeName; | ||
if (op == 'get') { | ||
// no-op, create the scope accessor | ||
} else if (op == 'delete') { | ||
apiName += '.destroyAll'; | ||
} else { | ||
apiName += '.' + op; | ||
} | ||
method.deprecated = 'Use ' + modelName + '.' + apiName + '() instead.'; | ||
// build a reverse record to be used in ngResource | ||
// Product.__find__categories -> Category.::find::product::categories | ||
var reverseName = '::' + op + '::' + modelName + '::' + scopeName; | ||
var reverseMethod = Object.create(method); | ||
reverseMethod.name = reverseName; | ||
delete reverseMethod.deprecated; | ||
reverseMethod.internal = 'Use ' + modelName + '.' + apiName + '() instead.'; | ||
var reverseModel = findModelByName(models, targetClass); | ||
reverseModel.methods.push(reverseMethod); | ||
var scopeMethod = Object.create(method); | ||
scopeMethod.name = reverseName; | ||
delete scopeMethod.deprecated; | ||
modelClass.scopes[scopeName].methods[apiName] = scopeMethod; | ||
} | ||
function findModelByName(models, name) { | ||
for (var n in models) { | ||
if (n.toLowerCase() == name.toLowerCase()) | ||
return models[n]; | ||
} | ||
} |
{ | ||
"name": "loopback-angular", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Tool for auto-generating Angular $resource services for LoopBack", | ||
"main": "index.js", | ||
"bin": { | ||
"lb-ng": "bin/lb-ng", | ||
"lb-ng-doc": "bin/lb-ng-doc" | ||
}, | ||
"scripts": { | ||
"pretest": "jshint . ; bower install", | ||
"test": "node test.e2e/test-server.js karma start --single-run --browsers PhantomJS" | ||
"test": "node ./test.e2e/test-server.js node_modules/karma/bin/karma start --single-run --browsers PhantomJS" | ||
}, | ||
@@ -31,29 +27,24 @@ "repository": { | ||
"dependencies": { | ||
"ejs": "~0.8.5" | ||
"ejs": "^1.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "*", | ||
"mocha": "*", | ||
"express": "~3.4.8", | ||
"loopback-datasource-juggler": "~1.3.0", | ||
"loopback": "~1.6.2", | ||
"cors": "~2.1.1", | ||
"karma-script-launcher": "~0.1.0", | ||
"karma-chrome-launcher": "~0.1.2", | ||
"karma-html2js-preprocessor": "~0.1.0", | ||
"karma-firefox-launcher": "~0.1.2", | ||
"karma-jasmine": "~0.1.5", | ||
"karma-coffee-preprocessor": "~0.1.1", | ||
"requirejs": "~2.1.9", | ||
"karma-requirejs": "~0.2.1", | ||
"karma-phantomjs-launcher": "~0.1.1", | ||
"karma": "~0.10.8", | ||
"karma-mocha": "~0.1.1", | ||
"mocha-as-promised": "~2.0.0", | ||
"bluebird": "~1.0.1", | ||
"debug": "~0.7.4", | ||
"fs.extra": "~1.2.1", | ||
"bower": "~1.2.8", | ||
"jshint": "~2.4.3" | ||
"bluebird": "^1.2", | ||
"bower": "^1.3", | ||
"chai": "^1.9", | ||
"cors": "^2.2", | ||
"debug": "^0.8", | ||
"express": "^3.5", | ||
"fs.extra": "^1.2", | ||
"jshint": "^2.4", | ||
"karma": "^0.12.2", | ||
"karma-chrome-launcher": "^0.1.2", | ||
"karma-junit-reporter": "^0.2.1", | ||
"karma-mocha": "^0.1.3", | ||
"karma-phantomjs-launcher": "^0.1.2", | ||
"karma-requirejs": "^0.2.1", | ||
"loopback": "^1.7", | ||
"loopback-datasource-juggler": "^1.3", | ||
"mocha": "^1.18", | ||
"requirejs": "^2.1.9" | ||
} | ||
} |
@@ -261,3 +261,201 @@ define(['angular', 'given', 'util'], function(angular, given, util) { | ||
}); | ||
describe('for models with hasAndBelongsToMany relations', function() { | ||
var $injector, Product, Category, testData; | ||
before(function() { | ||
return given.servicesForLoopBackApp( | ||
{ | ||
models: { | ||
Product: { | ||
properties: { name: 'string' }, | ||
options: { | ||
relations: { | ||
categories: { | ||
model: 'Category', | ||
type: 'hasAndBelongsToMany' | ||
} | ||
} | ||
} | ||
}, | ||
Category: { | ||
properties: { name: 'string' }, | ||
options: { | ||
relations: { | ||
products: { | ||
model: 'Product', | ||
type: 'hasAndBelongsToMany' | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
setupFn: (function(app, cb) { | ||
/*globals debug:true */ | ||
app.models.Product.create({ name: 'p1' }, function(err, prod) { | ||
if (err) return cb(err); | ||
debug('Created product', prod); | ||
prod.categories.create({ name: 'c1' }, function(err, cat) { | ||
if (err) return cb(err); | ||
debug('Created category', cat); | ||
prod.categories(true, function(err, list) { | ||
if (err) return cb(err); | ||
debug('Categories of product', list); | ||
cb(null, { | ||
product: prod, | ||
category: cat | ||
}); | ||
}); | ||
}); | ||
}); | ||
}).toString() | ||
}) | ||
.then(function(createInjector) { | ||
$injector = createInjector(); | ||
Product = $injector.get('Product'); | ||
Category = $injector.get('Category'); | ||
testData = $injector.get('testData'); | ||
}); | ||
}); | ||
it('provides scope methods', function() { | ||
expect(Object.keys(Product), 'Product properties') | ||
.to.contain('categories'); | ||
expect(Object.keys(Product.categories), 'Product.categories properties') | ||
.to.have.members([ | ||
'create', | ||
'destroyAll' | ||
]); | ||
}); | ||
it('gets related models with correct prototype', function() { | ||
var list = Product.categories({ id: testData.product.id }); | ||
return list.$promise.then(function() { | ||
// eql does not work for arrays with objects correctly :( | ||
expect(list).to.have.length(1); | ||
expect(list[0]).to.eql(new Category(testData.category)); | ||
}); | ||
}); | ||
it('creates a related model', function() { | ||
var cat = Product.categories.create( | ||
{ id: testData.product.id }, | ||
{ name: 'another-cat' }); | ||
return cat.$promise | ||
.then(function() { | ||
expect(cat).to.be.an.instanceof(Category); | ||
expect(cat).to.have.property('name', 'another-cat'); | ||
}) | ||
.then(function() { | ||
var list = Product.categories({ id: testData.product.id }); | ||
return list.$promise.then(function() { | ||
var names = list.map(function(c) { return c.name; }); | ||
expect(names).to.eql([testData.category.name, cat.name]); | ||
}); | ||
}); | ||
}); | ||
// Skipped due to strongloop/loopback-datasource-juggler#95 | ||
it.skip('removes all related models', function() { | ||
return Product.categories.destroyAll({ id: testData.product.id }) | ||
.$promise | ||
.then(function() { | ||
var list = Product.categories({ id: testData.product.id }); | ||
return list.$promise.then(function() { | ||
expect(list, 'product categories').to.have.length(0); | ||
}); | ||
}) | ||
.then(function() { | ||
var all = Product.find({ filter: true }); | ||
return all.$promise | ||
.then(function() { | ||
expect(all, 'all categories').to.have.length(0); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('for models with belongsTo relation', function() { | ||
var $injector, Town, Country, testData; | ||
before(function() { | ||
return given.servicesForLoopBackApp( | ||
{ | ||
models: { | ||
Town: { | ||
properties: { name: 'string' }, | ||
options: { | ||
relations: { | ||
country: { | ||
model: 'Country', | ||
type: 'belongsTo' | ||
} | ||
} | ||
} | ||
}, | ||
Country: { | ||
properties: { name: 'string' }, | ||
options: { | ||
relations: { | ||
towns: { | ||
model: 'Town', | ||
type: 'hasMany' | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
setupFn: (function(app, cb) { | ||
/*globals debug:true */ | ||
app.models.Country.create( | ||
{ name: 'a-country' }, | ||
function(err, country) { | ||
if (err) return cb(err); | ||
debug('Created country', country); | ||
country.towns.create({ name: 'a-town' }, | ||
function(err, town) { | ||
if (err) return cb(err); | ||
debug('Created town', town); | ||
town.country(true, function(err, res) { | ||
if (err) return cb(err); | ||
debug('Country of the town', res); | ||
cb(null, { | ||
country: country, | ||
town: town | ||
}); | ||
}); | ||
} | ||
); | ||
} | ||
); | ||
}).toString() | ||
}) | ||
.then(function(createInjector) { | ||
$injector = createInjector(); | ||
Town = $injector.get('Town'); | ||
Country = $injector.get('Country'); | ||
testData = $injector.get('testData'); | ||
}); | ||
}); | ||
it('provides scope methods', function() { | ||
expect(Object.keys(Town), 'Town properties') | ||
.to.contain('country'); | ||
}); | ||
it('gets the related model with the correct prototype', function() { | ||
var country = Town.country({ id: testData.town.id }); | ||
return country.$promise.then(function() { | ||
expect(country).to.be.instanceof(Country); | ||
for (var k in testData.country) { | ||
expect(country[k], 'country.' + k).to.equal(testData.country[k]); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -20,3 +20,2 @@ var tests = []; | ||
angularMocks: 'bower_components/angular-mocks/angular-mocks', | ||
mochaAsPromised: 'node_modules/mocha-as-promised/mocha-as-promised', | ||
given: 'test.e2e/given', | ||
@@ -46,5 +45,4 @@ util: 'test.e2e/util' | ||
require(['chai', 'mochaAsPromised'], function(chai, mochaAsPromised) { | ||
mochaAsPromised(window.Mocha); | ||
require(['chai'], function(chai) { | ||
window.expect = chai.expect; | ||
}); |
@@ -10,2 +10,3 @@ /* | ||
var generator = require('..'); | ||
var extend = require('util')._extend; | ||
@@ -24,2 +25,6 @@ var port = process.env.PORT || 3838; | ||
// Save the pre-build models so that we can restore them before every test | ||
var initialModels = loopback.Model.modelBuilder.models; | ||
var initialDefinitions = loopback.Model.modelBuilder.definitions; | ||
// Enable all domains to access our server via AJAX | ||
@@ -47,4 +52,13 @@ // This way the script running in Karma page can | ||
// other model objects | ||
} | ||
*/ | ||
}, | ||
setupFn: (function(app, cb) { | ||
Customer.create( | ||
{ name: 'a-customer' }, | ||
function(err, customer) { | ||
if (err) return cb(err); | ||
cb(null, { customer: customer }); | ||
}); | ||
}).toString() | ||
} | ||
*/ | ||
masterApp.post('/setup', function(req, res, next) { | ||
@@ -55,2 +69,3 @@ var opts = req.body; | ||
var enableAuth = opts.enableAuth; | ||
var setupFn = compileSetupFn(name, opts.setupFn); | ||
@@ -63,2 +78,6 @@ if (!name) | ||
// hack: clear the static model registry populated by previous test apps | ||
loopback.Model.modelBuilder.models = extend({}, initialModels); | ||
loopback.Model.modelBuilder.definitions = extend({}, initialDefinitions); | ||
lbApp = loopback(); | ||
@@ -82,12 +101,33 @@ | ||
try { | ||
servicesScript = generator.services(lbApp, name, apiUrl); | ||
} catch (err) { | ||
console.error('Cannot generate services script:', err.stack); | ||
servicesScript = 'throw new Error("Error generating services script.");'; | ||
} | ||
setupFn(lbApp, function(err, data) { | ||
if (err) { | ||
console.error('app setup function failed', err); | ||
res.send(500, err); | ||
return; | ||
} | ||
res.send(200, { servicesUrl: baseUrl + 'services?' + name }); | ||
try { | ||
servicesScript = generator.services(lbApp, name, apiUrl); | ||
} catch (err) { | ||
console.error('Cannot generate services script:', err.stack); | ||
servicesScript = 'throw new Error("Error generating services script.");'; | ||
} | ||
servicesScript += '\nangular.module(' + JSON.stringify(name) + ')' + | ||
'.value("testData", ' + JSON.stringify(data, null, 2) + ');\n'; | ||
res.send(200, { servicesUrl: baseUrl + 'services?' + name }); | ||
}.bind(this)); | ||
}); | ||
function compileSetupFn(name, source) { | ||
if (!source) | ||
return function(app, cb) { cb(); }; | ||
var debug = require('debug')('test:' + name); | ||
/*jshint evil:true */ | ||
return eval('(' + source + ')'); | ||
} | ||
masterApp.get('/services', function(req, res, next) { | ||
@@ -119,3 +159,4 @@ res.set('Content-Type', 'application/javascript'); | ||
child.on('error', function(err) { | ||
console.log(err); | ||
console.log('child_process.spawn failed', err); | ||
process.exit(1); | ||
}); | ||
@@ -122,0 +163,0 @@ child.on('exit', function() { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
69379
18
950
18
2