yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #01991
[Merge] lp:~benji/juju-gui/bug-1088507 into lp:juju-gui
Benji York has proposed merging lp:~benji/juju-gui/bug-1088507 into lp:juju-gui.
Requested reviews:
Juju GUI Hackers (juju-gui)
Related bugs:
Bug #1088507 in juju-gui: "Tests should pass on the production build of the code"
https://bugs.launchpad.net/juju-gui/+bug/1088507
For more details, see:
https://code.launchpad.net/~benji/juju-gui/bug-1088507/+merge/140020
Make the prod tests pass.
Changes a test that was failing because of some slight style difference to be
more direct. Some slight style differences still exist between prod and dev.
The charm model module (app/models/charm.js) is now included in the combined
and minified JS file.
The event-simulate and node-event-simulate functions are needed for the tests,
so they are now directly loaded by test/index.html.
The test_charm_configuration.js file needs juju-charm-models but that module
was not specified.
test/test_model.js had to be rearranged so the YUI().use method was called
inside the "before" method instead of around the entire test module. This
resulted in a mass reindenting.
Incidentally: Fixes the Makefile so YUI module assets are in the right place
so requests for them do not 404.
https://codereview.appspot.com/6947057/
--
https://code.launchpad.net/~benji/juju-gui/bug-1088507/+merge/140020
Your team Juju GUI Hackers is requested to review the proposed merge of lp:~benji/juju-gui/bug-1088507 into lp:juju-gui.
=== modified file 'Makefile'
--- Makefile 2012-12-14 18:04:28 +0000
+++ Makefile 2012-12-14 20:46:22 +0000
@@ -284,15 +284,13 @@
ln -sf "$(PWD)/build/juju-ui/assets/sprite.png" build-$(1)/juju-ui/assets/
ln -sf "$(PWD)/node_modules/yui/assets/skins/sam/rail-x.png" \
build-$(1)/juju-ui/assets/combined-css/rail-x.png
- # Link each YUI module's assets.
- mkdir -p build-$(1)/juju-ui/assets/skins/night/ \
- build-$(1)/juju-ui/assets/skins/sam/
- find node_modules/yui/ -path "*/skins/night/*" -type f \
- -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/skins/night/ \;
- find node_modules/yui/ -path "*/skins/sam/*" -type f \
- -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/skins/sam/ \;
- find node_modules/yui/ -path "*/assets/*" \! -path "*/skins/*" -type f \
- -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/ \;
+ # Copy each YUI module's assets to a parallel directory in the build
+ # location. This is run in a subshell (indicated by the parenthesis)
+ # so we can change directory and have it not effect this process. To
+ # understand how it does what it does look at the man page for cp,
+ # particularly "--parents".
+ (cd node_modules/yui/ && \
+ cp -r --parents */assets "$(PWD)/build-$(1)/juju-ui/assets/")
endef
$(LINK_DEBUG_FILES):
=== modified file 'app/app.js'
--- app/app.js 2012-12-04 22:08:40 +0000
+++ app/app.js 2012-12-14 20:46:22 +0000
@@ -761,6 +761,7 @@
}, '0.5.2', {
requires: [
'juju-models',
+ 'juju-charm-models',
'juju-views',
'juju-controllers',
'juju-view-charm-search',
=== modified file 'app/views/charm-panel.js'
--- app/views/charm-panel.js 2012-12-03 22:24:16 +0000
+++ app/views/charm-panel.js 2012-12-14 20:46:22 +0000
@@ -697,6 +697,31 @@
'input.config-field[type=checkbox]':
{click: function(evt) {evt.target.focus();}}
},
+ /**
+ * Determines the Y coordinate that would center a tooltip on a field.
+ *
+ * @static
+ * @param {Number} fieldY The current Y position of the tooltip.
+ * @param {Number} fieldHeight The hight of the field.
+ * @param {Number} tooltipHeight The height of the tooltip.
+ * @return {Number} New Y coordinate for the tooltip.
+ */
+ _calculateTooltipY: function(fieldY, fieldHeight, tooltipHeight) {
+ var y_offset = (tooltipHeight - fieldHeight) / 2;
+ return fieldY - y_offset;
+ },
+ /**
+ * Determines the X coordinate that would place a tooltip next to a
+ * field.
+ *
+ * @static
+ * @param {Number} fieldX The current X position of the tooltip.
+ * @param {Number} tooltipWidth The width of the tooltip.
+ * @return {Number} New X coordinate for the tooltip.
+ */
+ _calculateTooltipX: function(fieldX, tooltipWidth) {
+ return fieldX - tooltipWidth - 15;
+ },
_moveTooltip: function() {
if (this.tooltip.field &&
Y.DOM.inRegion(
@@ -708,10 +733,13 @@
var widget = this.tooltip.get('boundingBox'),
tooltipWidth = widget.get('clientWidth'),
tooltipHeight = widget.get('clientHeight'),
- y_offset = (tooltipHeight - fieldHeight) / 2;
- this.tooltip.move( // These are the x, y coordinates.
- [this.tooltip.panel.getX() - tooltipWidth - 15,
- this.tooltip.field.getY() - y_offset]);
+ fieldX = this.tooltip.panel.getX(),
+ fieldY = this.tooltip.field.getY(),
+ tooltipX = this._calculateTooltipX(
+ fieldX, tooltipWidth),
+ tooltipY = this._calculateTooltipY(
+ fieldY, fieldHeight, tooltipHeight);
+ this.tooltip.move([tooltipX, tooltipY]);
if (!this.tooltip.get('visible')) {
this.tooltip.show();
}
=== modified file 'bin/merge-files'
--- bin/merge-files 2012-12-10 17:21:32 +0000
+++ bin/merge-files 2012-12-14 20:46:22 +0000
@@ -47,6 +47,7 @@
// templates.js is a generated file. It is not part of the app directory.
paths.push(syspath.join(process.cwd(), 'build/juju-ui/templates.js'));
+ paths.push(syspath.join(process.cwd(), 'app/models/charm.js'));
merge.combineJs(paths, 'build/juju-ui/assets/app.js');
=== modified file 'test/index.html'
--- test/index.html 2012-12-11 04:11:39 +0000
+++ test/index.html 2012-12-14 20:46:22 +0000
@@ -3,8 +3,10 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="assets/mocha.css">
+ <script src="/juju-ui/assets/modules.js"></script>
<script src="/juju-ui/assets/all-yui.js"></script>
- <script src="/juju-ui/assets/modules.js"></script>
+ <script src="/juju-ui/assets/event-simulate.js"></script>
+ <script src="/juju-ui/assets/node-event-simulate.js"></script>
<script src="assets/chai.js"></script>
<script src="assets/mocha.js"></script>
<script src="utils.js"></script>
=== modified file 'test/test_charm_configuration.js'
--- test/test_charm_configuration.js 2012-10-29 10:01:29 +0000
+++ test/test_charm_configuration.js 2012-12-14 20:46:22 +0000
@@ -24,6 +24,7 @@
before(function(done) {
Y = YUI(GlobalConfig).use(
'juju-models',
+ 'juju-charm-models',
'juju-views',
'juju-gui',
'juju-env',
@@ -199,35 +200,25 @@
tooltip.get('visible').should.equal(true);
});
- it('must keep the tooltip aligned with its field', function() {
- var charm = new models.Charm({id: 'precise/mysql-7'}),
- view = new views.CharmConfigurationView(
- { container: container,
- model: charm,
- tooltipDelay: 0 });
- charm.setAttrs(charmConfig);
- charm.loaded = true;
- view.render();
- var tooltip = view.tooltip,
- controls = container.all('.control-group input'),
- panel = container.one('.charm-panel');
- // The panel needs to be scrollable and smaller than what it contains. We
- // do this by setting a height to the panel and then setting the height to
- // one of the controls to something much bigger.
- panel.setStyles({height: '400px', overflowY: 'auto'});
- controls.item(2).set('height', '4000px');
- // We need to have the field visible or else the call to "focus" will
- // change the positioning after our calculation has occurred, thus
- // changing our Y field.
- controls.item(1).scrollIntoView();
- controls.item(1).focus();
- var originalY = tooltip.get('boundingBox').getY();
- panel.set('scrollTop', panel.get('scrollTop') + 10);
- // The simulate module does not support firing scroll events so we call
- // the associated method directly.
- view._moveTooltip();
- Math.floor(tooltip.get('boundingBox').getY())
- .should.equal(Math.floor(originalY - 10));
+ it('must keep the tooltip aligned with its field vertically', function() {
+ // The tooltip's Y coordinate should be such that it is centered vertically
+ // on its associated field.
+ var fieldHeight = 7;
+ var tooltipHeight = 17;
+ var fieldY = 1000;
+ var view = new views.CharmConfigurationView();
+ var y = view._calculateTooltipY(fieldY, fieldHeight, tooltipHeight);
+ assert.equal(y, 995);
+ });
+
+ it('must keep the tooltip to the left of its field', function() {
+ // The tooltip's X coordinate should be such that it is to the left of its
+ // associated field.
+ var tooltipWidth = 100;
+ var fieldX = 1000;
+ var view = new views.CharmConfigurationView();
+ var x = view._calculateTooltipX(fieldX, tooltipWidth);
+ assert.equal(x, 885);
});
it('must hide the tooltip when its field scrolls away', function() {
=== modified file 'test/test_model.js'
--- test/test_model.js 2012-11-23 16:21:32 +0000
+++ test/test_model.js 2012-12-14 20:46:22 +0000
@@ -1,356 +1,359 @@
'use strict';
-YUI(GlobalConfig).use('juju-models', function(Y) {
- describe('charm normalization', function() {
- var models;
-
- before(function() {
- models = Y.namespace('juju.models');
- });
-
- it('must create derived attributes from official charm id', function() {
- var charm = new models.Charm(
- {id: 'cs:precise/openstack-dashboard-0'});
- charm.get('scheme').should.equal('cs');
- var _ = expect(charm.get('owner')).to.not.exist;
- charm.get('full_name').should.equal('precise/openstack-dashboard');
- charm.get('charm_store_path').should.equal(
- 'charms/precise/openstack-dashboard-0/json');
- });
-
- it('must convert timestamps into time objects', function() {
- var time = 1349797266.032,
- date = new Date(time),
- charm = new models.Charm(
- { id: 'cs:precise/foo-9', last_change: {created: time / 1000} });
- charm.get('last_change').created.should.eql(date);
- });
-
- });
-});
-
-YUI(GlobalConfig).use('juju-models', function(Y) {
- describe('juju models', function() {
- var models;
-
- before(function() {
- models = Y.namespace('juju.models');
- });
-
- it('must be able to create charm', function() {
- var charm = new models.Charm(
- {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
- charm.get('scheme').should.equal('cs');
- charm.get('owner').should.equal('alt-bac');
- charm.get('series').should.equal('precise');
- charm.get('package_name').should.equal('openstack-dashboard');
- charm.get('revision').should.equal(0);
- charm.get('full_name').should.equal(
- '~alt-bac/precise/openstack-dashboard');
- charm.get('charm_store_path').should.equal(
- '~alt-bac/precise/openstack-dashboard-0/json');
- });
-
- it('must be able to parse real-world charm names', function() {
- var charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});
- charm.get('full_name').should.equal('precise/openstack-dashboard');
- charm.get('package_name').should.equal('openstack-dashboard');
- charm.get('charm_store_path').should.equal(
- 'charms/precise/openstack-dashboard-0/json');
- charm.get('scheme').should.equal('cs');
- var _ = expect(charm.get('owner')).to.not.exist;
- charm.get('series').should.equal('precise');
- charm.get('package_name').should.equal('openstack-dashboard');
- charm.get('revision').should.equal(0);
- });
-
- it('must be able to parse individually owned charms', function() {
- // Note that an earlier version of the parsing code did not handle
- // hyphens in user names, so this test intentionally includes one.
- var charm = new models.Charm(
- {id: 'cs:~marco-ceppi/precise/wordpress-17'});
- charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
- charm.get('package_name').should.equal('wordpress');
- charm.get('charm_store_path').should.equal(
- '~marco-ceppi/precise/wordpress-17/json');
- charm.get('revision').should.equal(17);
- });
-
- it('must reject bad charm ids.', function() {
- try {
- var charm = new models.Charm({id: 'foobar'});
- assert.fail('Should have thrown an error');
- } catch (e) {
- e.should.equal(
- 'Developers must initialize charms with a well-formed id.');
- }
- });
-
- it('must reject missing charm ids at initialization.', function() {
- try {
- var charm = new models.Charm();
- assert.fail('Should have thrown an error');
- } catch (e) {
- e.should.equal(
- 'Developers must initialize charms with a well-formed id.');
- }
- });
-
- it('must be able to create charm list', function() {
- var c1 = new models.Charm(
- { id: 'cs:precise/mysql-2',
- description: 'A DB'}),
- c2 = new models.Charm(
- { id: 'cs:precise/logger-3',
- description: 'Log sub'}),
- clist = new models.CharmList();
- clist.add([c1, c2]);
- var names = clist.map(function(c) {return c.get('package_name');});
- names[0].should.equal('mysql');
- names[1].should.equal('logger');
- });
-
-
- it('service unit list should be able to get units of a given service',
- function() {
- var sl = new models.ServiceList();
- var sul = new models.ServiceUnitList();
- var mysql = new models.Service({id: 'mysql'});
- var wordpress = new models.Service({id: 'wordpress'});
- sl.add([mysql, wordpress]);
- sl.getById('mysql').should.equal(mysql);
- sl.getById('wordpress').should.equal(wordpress);
-
- sul.add([{id: 'mysql/0'}, {id: 'mysql/1'}]);
-
- var wp0 = {id: 'wordpress/0'},
- wp1 = {id: 'wordpress/1'};
- sul.add([wp0, wp1]);
- wp0.service.should.equal('wordpress');
-
- sul.get_units_for_service(mysql, true).getAttrs(['id']).id.should.eql(
- ['mysql/0', 'mysql/1']);
- sul.get_units_for_service(wordpress, true).getAttrs(
- ['id']).id.should.eql(['wordpress/0', 'wordpress/1']);
- });
-
- it('service unit list should be able to aggregate unit statuses',
- function() {
- var sl = new models.ServiceList();
- var sul = new models.ServiceUnitList();
- var mysql = new models.Service({id: 'mysql'});
- var wordpress = new models.Service({id: 'wordpress'});
- sl.add([mysql, wordpress]);
-
- var my0 = new models.ServiceUnit(
- {id: 'mysql/0', agent_state: 'pending'}),
- my1 = new models.ServiceUnit(
- {id: 'mysql/1', agent_state: 'pending'});
-
- sul.add([my0, my1]);
-
- var wp0 = new models.ServiceUnit(
- { id: 'wordpress/0',
- agent_state: 'pending'}),
- wp1 = new models.ServiceUnit(
- { id: 'wordpress/1',
- agent_state: 'error'});
- sul.add([wp0, wp1]);
-
- sul.get_informative_states_for_service(mysql).should.eql(
- {'pending': 2});
- sul.get_informative_states_for_service(wordpress).should.eql(
- {'pending': 1, 'error': 1});
- });
-
- it('service unit objects should parse the service name from unit id',
- function() {
- var service_unit = {id: 'mysql/0'},
- db = new models.Database();
- db.units.add(service_unit);
- service_unit.service.should.equal('mysql');
- });
-
- it('service unit objects should report their number correctly',
- function() {
- var service_unit = {id: 'mysql/5'},
- db = new models.Database();
- db.units.add(service_unit);
- service_unit.number.should.equal(5);
- });
-
- it('must be able to resolve models by modelId', function() {
- var db = new models.Database();
-
- db.services.add([{id: 'wordpress'}, {id: 'mediawiki'}]);
- db.units.add([{id: 'wordpress/0'}, {id: 'wordpress/1'}]);
-
- var model = db.services.item(0);
- // Single parameter calling
- db.getModelById([model.name, model.get('id')])
- .get('id').should.equal('wordpress');
- // Two parameter interface
- db.getModelById(model.name, model.get('id'))
- .get('id').should.equal('wordpress');
-
- var unit = db.units.item(0);
- db.getModelById([unit.name, unit.id]).id.should.equal('wordpress/0');
- db.getModelById(unit.name, unit.id).id.should.equal('wordpress/0');
- });
-
- it('on_delta should handle remove changes correctly',
- function() {
- var db = new models.Database();
- var my0 = new models.ServiceUnit({id: 'mysql/0',
- agent_state: 'pending'}),
- my1 = new models.ServiceUnit({id: 'mysql/1',
- agent_state: 'pending'});
- db.units.add([my0, my1]);
- db.on_delta({data: {result: [
- ['unit', 'remove', 'mysql/1']
- ]}});
- var names = db.units.get('id');
- names.length.should.equal(1);
- names[0].should.equal('mysql/0');
- });
-
- it('on_delta should be able to reuse existing services with add',
- function() {
- var db = new models.Database();
- var my0 = new models.Service({id: 'mysql', exposed: true});
- db.services.add([my0]);
- // Note that exposed is not set explicitly to false.
- db.on_delta({data: {result: [
- ['service', 'add', {id: 'mysql'}]
- ]}});
- my0.get('exposed').should.equal(false);
- });
-
- it('on_delta should be able to reuse existing units with add',
- // Units are special because they use the LazyModelList.
- function() {
- var db = new models.Database();
- var my0 = {id: 'mysql/0', agent_state: 'pending'};
- db.units.add([my0]);
- db.on_delta({data: {result: [
- ['unit', 'add', {id: 'mysql/0', agent_state: 'another'}]
- ]}});
- my0.agent_state.should.equal('another');
- });
-
- it('on_delta should reset relation_errors',
- function() {
- var db = new models.Database();
- var my0 = {id: 'mysql/0', relation_errors: {'cache': ['memcached']}};
- db.units.add([my0]);
- // Note that relation_errors is not set.
- db.on_delta({data: {result: [
- ['unit', 'change', {id: 'mysql/0'}]
- ]}});
- my0.relation_errors.should.eql({});
- });
-
- it('ServiceUnitList should accept a list of units at instantiation and ' +
- 'decorate them', function() {
- var mysql = new models.Service({id: 'mysql'});
- var objs = [{id: 'mysql/0'},
- {id: 'mysql/1'}];
- var sul = new models.ServiceUnitList({items: objs});
- var unit_data = sul.get_units_for_service(
- mysql, true).getAttrs(['service', 'number']);
- unit_data.service.should.eql(['mysql', 'mysql']);
- unit_data.number.should.eql([0, 1]);
- });
-
- it('RelationList.has_relations.. should return true if rel found.',
- function() {
- var db = new models.Database(),
- service = new models.Service({id: 'mysql', exposed: false}),
- rel0 = new models.Relation({
- id: 'relation-0',
- endpoints: [
- ['mediawiki', {name: 'cache', role: 'source'}],
- ['squid', {name: 'cache', role: 'front'}]],
- 'interface': 'cache'
- }),
- rel1 = new models.Relation({
- id: 'relation-4',
- endpoints: [
- ['something', {name: 'foo', role: 'bar'}],
- ['mysql', {name: 'la', role: 'lee'}]],
- 'interface': 'thing'
- });
- db.relations.add([rel0, rel1]);
- db.relations.has_relation_for_endpoint(
- {service: 'squid', name: 'cache', type: 'cache'}
- ).should.equal(true);
- db.relations.has_relation_for_endpoint(
- {service: 'mysql', name: 'la', type: 'thing'}
- ).should.equal(true);
- db.relations.has_relation_for_endpoint(
- {service: 'squid', name: 'cache', type: 'http'}
- ).should.equal(false);
-
- // We can also pass a service name which must match for the
- // same relation.
-
- db.relations.has_relation_for_endpoint(
- {service: 'squid', name: 'cache', type: 'cache'},
- 'kafka'
- ).should.equal(false);
-
- db.relations.has_relation_for_endpoint(
- {service: 'squid', name: 'cache', type: 'cache'},
- 'mediawiki'
- ).should.equal(true);
-
- });
-
- it('RelationList.get_relations_for_service should do what it says',
- function() {
- var db = new models.Database(),
- service = new models.Service({id: 'mysql', exposed: false}),
- rel0 = new models.Relation(
- { id: 'relation-0',
- endpoints:
- [['mediawiki', {name: 'cache', role: 'source'}],
- ['squid', {name: 'cache', role: 'front'}]],
- 'interface': 'cache'
- }),
- rel1 = new models.Relation(
- { id: 'relation-1',
- endpoints:
- [['wordpress', {role: 'peer', name: 'loadbalancer'}]],
- 'interface': 'reversenginx'
- }),
- rel2 = new models.Relation(
- { id: 'relation-2',
- endpoints:
- [['mysql', {name: 'db', role: 'db'}],
- ['mediawiki', {name: 'storage', role: 'app'}]],
- 'interface': 'db'
- }),
- rel3 = new models.Relation(
- { id: 'relation-3',
- endpoints:
- [['mysql', {role: 'peer', name: 'loadbalancer'}]],
- 'interface': 'mysql-loadbalancer'
- }),
- rel4 = new models.Relation(
- { id: 'relation-4',
- endpoints:
- [['something', {name: 'foo', role: 'bar'}],
- ['mysql', {name: 'la', role: 'lee'}]],
- 'interface': 'thing'
- });
- db.relations.add([rel0, rel1, rel2, rel3, rel4]);
- Y.Array.map(
- db.relations.get_relations_for_service(service),
- function(r) { return r.get('id'); })
- .should.eql(['relation-2', 'relation-3', 'relation-4']);
- });
- });
-});
+describe('charm normalization', function() {
+ var models;
+
+ before(function() {
+ YUI(GlobalConfig).use('juju-models', 'juju-charm-models', function(Y) {
+ models = Y.namespace('juju.models');
+ });
+ });
+
+ it('must create derived attributes from official charm id', function() {
+ var charm = new models.Charm(
+ {id: 'cs:precise/openstack-dashboard-0'});
+ charm.get('scheme').should.equal('cs');
+ var _ = expect(charm.get('owner')).to.not.exist;
+ charm.get('full_name').should.equal('precise/openstack-dashboard');
+ charm.get('charm_store_path').should.equal(
+ 'charms/precise/openstack-dashboard-0/json');
+ });
+
+ it('must convert timestamps into time objects', function() {
+ var time = 1349797266.032,
+ date = new Date(time),
+ charm = new models.Charm(
+ { id: 'cs:precise/foo-9', last_change: {created: time / 1000} });
+ charm.get('last_change').created.should.eql(date);
+ });
+
+});
+
+describe('juju models', function() {
+ var models;
+
+ before(function(done) {
+ YUI(GlobalConfig).use('juju-models', 'juju-charm-models', function(Y) {
+ models = Y.namespace('juju.models');
+ done();
+ });
+ });
+
+ it('must be able to create charm', function() {
+ var charm = new models.Charm(
+ {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
+ charm.get('scheme').should.equal('cs');
+ charm.get('owner').should.equal('alt-bac');
+ charm.get('series').should.equal('precise');
+ charm.get('package_name').should.equal('openstack-dashboard');
+ charm.get('revision').should.equal(0);
+ charm.get('full_name').should.equal(
+ '~alt-bac/precise/openstack-dashboard');
+ charm.get('charm_store_path').should.equal(
+ '~alt-bac/precise/openstack-dashboard-0/json');
+ });
+
+ it('must be able to parse real-world charm names', function() {
+ var charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});
+ charm.get('full_name').should.equal('precise/openstack-dashboard');
+ charm.get('package_name').should.equal('openstack-dashboard');
+ charm.get('charm_store_path').should.equal(
+ 'charms/precise/openstack-dashboard-0/json');
+ charm.get('scheme').should.equal('cs');
+ var _ = expect(charm.get('owner')).to.not.exist;
+ charm.get('series').should.equal('precise');
+ charm.get('package_name').should.equal('openstack-dashboard');
+ charm.get('revision').should.equal(0);
+ });
+
+ it('must be able to parse individually owned charms', function() {
+ // Note that an earlier version of the parsing code did not handle
+ // hyphens in user names, so this test intentionally includes one.
+ var charm = new models.Charm(
+ {id: 'cs:~marco-ceppi/precise/wordpress-17'});
+ charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
+ charm.get('package_name').should.equal('wordpress');
+ charm.get('charm_store_path').should.equal(
+ '~marco-ceppi/precise/wordpress-17/json');
+ charm.get('revision').should.equal(17);
+ });
+
+ it('must reject bad charm ids.', function() {
+ try {
+ var charm = new models.Charm({id: 'foobar'});
+ assert.fail('Should have thrown an error');
+ } catch (e) {
+ e.should.equal(
+ 'Developers must initialize charms with a well-formed id.');
+ }
+ });
+
+ it('must reject missing charm ids at initialization.', function() {
+ try {
+ var charm = new models.Charm();
+ assert.fail('Should have thrown an error');
+ } catch (e) {
+ e.should.equal(
+ 'Developers must initialize charms with a well-formed id.');
+ }
+ });
+
+ it('must be able to create charm list', function() {
+ var c1 = new models.Charm(
+ { id: 'cs:precise/mysql-2',
+ description: 'A DB'}),
+ c2 = new models.Charm(
+ { id: 'cs:precise/logger-3',
+ description: 'Log sub'}),
+ clist = new models.CharmList();
+ clist.add([c1, c2]);
+ var names = clist.map(function(c) {return c.get('package_name');});
+ names[0].should.equal('mysql');
+ names[1].should.equal('logger');
+ });
+
+
+ it('service unit list should be able to get units of a given service',
+ function() {
+ var sl = new models.ServiceList();
+ var sul = new models.ServiceUnitList();
+ var mysql = new models.Service({id: 'mysql'});
+ var wordpress = new models.Service({id: 'wordpress'});
+ sl.add([mysql, wordpress]);
+ sl.getById('mysql').should.equal(mysql);
+ sl.getById('wordpress').should.equal(wordpress);
+
+ sul.add([{id: 'mysql/0'}, {id: 'mysql/1'}]);
+
+ var wp0 = {id: 'wordpress/0'};
+ var wp1 = {id: 'wordpress/1'};
+ sul.add([wp0, wp1]);
+ wp0.service.should.equal('wordpress');
+
+ sul.get_units_for_service(mysql, true).getAttrs(['id']).id.should.eql(
+ ['mysql/0', 'mysql/1']);
+ sul.get_units_for_service(wordpress, true).getAttrs(
+ ['id']).id.should.eql(['wordpress/0', 'wordpress/1']);
+ });
+
+ it('service unit list should be able to aggregate unit statuses',
+ function() {
+ var sl = new models.ServiceList();
+ var sul = new models.ServiceUnitList();
+ var mysql = new models.Service({id: 'mysql'});
+ var wordpress = new models.Service({id: 'wordpress'});
+ sl.add([mysql, wordpress]);
+
+ var my0 = new models.ServiceUnit({
+ id: 'mysql/0',
+ agent_state: 'pending'});
+ var my1 = new models.ServiceUnit({
+ id: 'mysql/1',
+ agent_state: 'pending'});
+
+ sul.add([my0, my1]);
+
+ var wp0 = new models.ServiceUnit({
+ id: 'wordpress/0',
+ agent_state: 'pending'});
+ var wp1 = new models.ServiceUnit({
+ id: 'wordpress/1',
+ agent_state: 'error'});
+ sul.add([wp0, wp1]);
+
+ sul.get_informative_states_for_service(mysql).should.eql(
+ {'pending': 2});
+ sul.get_informative_states_for_service(wordpress).should.eql(
+ {'pending': 1, 'error': 1});
+ });
+
+ it('service unit objects should parse the service name from unit id',
+ function() {
+ var service_unit = {id: 'mysql/0'};
+ var db = new models.Database();
+ db.units.add(service_unit);
+ service_unit.service.should.equal('mysql');
+ });
+
+ it('service unit objects should report their number correctly',
+ function() {
+ var service_unit = {id: 'mysql/5'};
+ var db = new models.Database();
+ db.units.add(service_unit);
+ service_unit.number.should.equal(5);
+ });
+
+ it('must be able to resolve models by modelId', function() {
+ var db = new models.Database();
+
+ db.services.add([{id: 'wordpress'}, {id: 'mediawiki'}]);
+ db.units.add([{id: 'wordpress/0'}, {id: 'wordpress/1'}]);
+
+ var model = db.services.item(0);
+ // Single parameter calling
+ db.getModelById([model.name, model.get('id')])
+ .get('id').should.equal('wordpress');
+ // Two parameter interface
+ db.getModelById(model.name, model.get('id'))
+ .get('id').should.equal('wordpress');
+
+ var unit = db.units.item(0);
+ db.getModelById([unit.name, unit.id]).id.should.equal('wordpress/0');
+ db.getModelById(unit.name, unit.id).id.should.equal('wordpress/0');
+ });
+
+ it('on_delta should handle remove changes correctly',
+ function() {
+ var db = new models.Database();
+ var my0 = new models.ServiceUnit({id: 'mysql/0',
+ agent_state: 'pending'});
+ var my1 = new models.ServiceUnit({id: 'mysql/1',
+ agent_state: 'pending'});
+ db.units.add([my0, my1]);
+ db.on_delta({data: {result: [
+ ['unit', 'remove', 'mysql/1']
+ ]}});
+ var names = db.units.get('id');
+ names.length.should.equal(1);
+ names[0].should.equal('mysql/0');
+ });
+
+ it('on_delta should be able to reuse existing services with add',
+ function() {
+ var db = new models.Database();
+ var my0 = new models.Service({id: 'mysql', exposed: true});
+ db.services.add([my0]);
+ // Note that exposed is not set explicitly to false.
+ db.on_delta({data: {result: [
+ ['service', 'add', {id: 'mysql'}]
+ ]}});
+ my0.get('exposed').should.equal(false);
+ });
+
+ it('on_delta should be able to reuse existing units with add',
+ // Units are special because they use the LazyModelList.
+ function() {
+ var db = new models.Database();
+ var my0 = {id: 'mysql/0', agent_state: 'pending'};
+ db.units.add([my0]);
+ db.on_delta({data: {result: [
+ ['unit', 'add', {id: 'mysql/0', agent_state: 'another'}]
+ ]}});
+ my0.agent_state.should.equal('another');
+ });
+
+ it('on_delta should reset relation_errors',
+ function() {
+ var db = new models.Database();
+ var my0 = {id: 'mysql/0', relation_errors: {'cache': ['memcached']}};
+ db.units.add([my0]);
+ // Note that relation_errors is not set.
+ db.on_delta({data: {result: [
+ ['unit', 'change', {id: 'mysql/0'}]
+ ]}});
+ my0.relation_errors.should.eql({});
+ });
+
+ it('ServiceUnitList should accept a list of units at instantiation and ' +
+ 'decorate them', function() {
+ var mysql = new models.Service({id: 'mysql'});
+ var objs = [{id: 'mysql/0'},
+ {id: 'mysql/1'}];
+ var sul = new models.ServiceUnitList({items: objs});
+ var unit_data = sul.get_units_for_service(
+ mysql, true).getAttrs(['service', 'number']);
+ unit_data.service.should.eql(['mysql', 'mysql']);
+ unit_data.number.should.eql([0, 1]);
+ });
+
+ it('RelationList.has_relations.. should return true if rel found.',
+ function() {
+ var db = new models.Database(),
+ service = new models.Service({id: 'mysql', exposed: false}),
+ rel0 = new models.Relation({
+ id: 'relation-0',
+ endpoints: [
+ ['mediawiki', {name: 'cache', role: 'source'}],
+ ['squid', {name: 'cache', role: 'front'}]],
+ 'interface': 'cache'
+ }),
+ rel1 = new models.Relation({
+ id: 'relation-4',
+ endpoints: [
+ ['something', {name: 'foo', role: 'bar'}],
+ ['mysql', {name: 'la', role: 'lee'}]],
+ 'interface': 'thing'
+ });
+ db.relations.add([rel0, rel1]);
+ db.relations.has_relation_for_endpoint(
+ {service: 'squid', name: 'cache', type: 'cache'}
+ ).should.equal(true);
+ db.relations.has_relation_for_endpoint(
+ {service: 'mysql', name: 'la', type: 'thing'}
+ ).should.equal(true);
+ db.relations.has_relation_for_endpoint(
+ {service: 'squid', name: 'cache', type: 'http'}
+ ).should.equal(false);
+
+ // We can also pass a service name which must match for the
+ // same relation.
+
+ db.relations.has_relation_for_endpoint(
+ {service: 'squid', name: 'cache', type: 'cache'},
+ 'kafka'
+ ).should.equal(false);
+
+ db.relations.has_relation_for_endpoint(
+ {service: 'squid', name: 'cache', type: 'cache'},
+ 'mediawiki'
+ ).should.equal(true);
+
+ });
+
+ it('RelationList.get_relations_for_service should do what it says',
+ function() {
+ var db = new models.Database(),
+ service = new models.Service({id: 'mysql', exposed: false}),
+ rel0 = new models.Relation(
+ { id: 'relation-0',
+ endpoints:
+ [['mediawiki', {name: 'cache', role: 'source'}],
+ ['squid', {name: 'cache', role: 'front'}]],
+ 'interface': 'cache'
+ }),
+ rel1 = new models.Relation(
+ { id: 'relation-1',
+ endpoints:
+ [['wordpress', {role: 'peer', name: 'loadbalancer'}]],
+ 'interface': 'reversenginx'
+ }),
+ rel2 = new models.Relation(
+ { id: 'relation-2',
+ endpoints:
+ [['mysql', {name: 'db', role: 'db'}],
+ ['mediawiki', {name: 'storage', role: 'app'}]],
+ 'interface': 'db'
+ }),
+ rel3 = new models.Relation(
+ { id: 'relation-3',
+ endpoints:
+ [['mysql', {role: 'peer', name: 'loadbalancer'}]],
+ 'interface': 'mysql-loadbalancer'
+ }),
+ rel4 = new models.Relation(
+ { id: 'relation-4',
+ endpoints:
+ [['something', {name: 'foo', role: 'bar'}],
+ ['mysql', {name: 'la', role: 'lee'}]],
+ 'interface': 'thing'
+ });
+ db.relations.add([rel0, rel1, rel2, rel3, rel4]);
+ db.relations.get_relations_for_service(service).map(
+ function(r) { return r.get('id'); })
+ .should.eql(['relation-2', 'relation-3', 'relation-4']);
+ });
+});
+
YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
'juju-tests-utils', 'json-stringify',
Follow ups