Merge lp:~benji/juju-gui/bug-1088507 into lp:juju-gui/experimental

Proposed by Benji York
Status: Merged
Merged at revision: 285
Proposed branch: lp:~benji/juju-gui/bug-1088507
Merge into: lp:juju-gui/experimental
Diff against target: 925 lines (+433/-395)
7 files modified
Makefile (+19/-10)
app/app.js (+1/-0)
app/views/charm-panel.js (+32/-4)
bin/merge-files (+2/-0)
test/index.html (+5/-1)
test/test_charm_configuration.js (+20/-29)
test/test_model.js (+354/-351)
To merge this branch: bzr merge lp:~benji/juju-gui/bug-1088507
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+140020@code.launchpad.net

Description of the change

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/

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Land with changes.

Thank you for accomplishing this difficult task.

In addition to the comments below, please add a request in process.rst
that reviewers run both prod tests and debug tests. Then, in the
Makefile, either make the default test the prod test, or make it thumb
its nose at us and tell us to run one of the other targets.

Gary

https://codereview.appspot.com/6947057/diff/1/Makefile
File Makefile (right):

https://codereview.appspot.com/6947057/diff/1/Makefile#newcode293
Makefile:293: cp -r --parents */assets
"$(PWD)/build-$(1)/juju-ui/assets/")
Looks nicer to me, thanks.

https://codereview.appspot.com/6947057/diff/1/bin/merge-files
File bin/merge-files (right):

https://codereview.appspot.com/6947057/diff/1/bin/merge-files#newcode50
bin/merge-files:50: paths.push(syspath.join(process.cwd(),
'app/models/charm.js'));
As we discussed, please either figure out why readdir is not finding
charm.js, or put an XXX and a bug, and we will come back to this.

https://codereview.appspot.com/6947057/diff/1/test/index.html
File test/index.html (right):

https://codereview.appspot.com/6947057/diff/1/test/index.html#newcode9
test/index.html:9: <script
src="/juju-ui/assets/node-event-simulate.js"></script>
You explained the horror behind these two inclusions. Please add a
comment so others can enjoy.

https://codereview.appspot.com/6947057/

Revision history for this message
Nicola Larosa (teknico) wrote :

Needs fixing.

Running "make test-prod" produces 60 failures here, which is admittedly
a nice improvement down from 79, but still not enough. :-)

You most likely don't see these failures: I'd like to understand why I
do.

There's one more thing I'd like to clarify, see comment inline.

https://codereview.appspot.com/6947057/diff/1/Makefile
File Makefile (right):

https://codereview.appspot.com/6947057/diff/1/Makefile#newcode293
Makefile:293: cp -r --parents */assets
"$(PWD)/build-$(1)/juju-ui/assets/")
There's been some back and forth on this. You originally put in the "cp"
instruction; then I wanted to have symlinks in the build directories
rather than copies, so I spent some effort to come up with the "find"
command that created the right links in the right places, and did not
see requests for them return 404. Your experience has apparently been
different. I'd like to understand: 1) why; 2) if there's value enough,
in having symlinks instead of copies, to worry about this.

https://codereview.appspot.com/6947057/

lp:~benji/juju-gui/bug-1088507 updated
278. By Benji York

Review change: remove the "test" target and have it tell the user to be explicit about what they want

279. By Benji York

add more commentary from reveis

280. By Benji York

add comment suggested in review

281. By Benji York

add some test tiles to the build directories

Revision history for this message
Nicola Larosa (teknico) wrote :

It works now, thanks. Do not worry about the other issue, we'll worry
about it later.

Land this.

https://codereview.appspot.com/6947057/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2012-12-14 18:04:28 +0000
+++ Makefile 2012-12-17 14:57:47 +0000
@@ -284,15 +284,20 @@
284 ln -sf "$(PWD)/build/juju-ui/assets/sprite.png" build-$(1)/juju-ui/assets/284 ln -sf "$(PWD)/build/juju-ui/assets/sprite.png" build-$(1)/juju-ui/assets/
285 ln -sf "$(PWD)/node_modules/yui/assets/skins/sam/rail-x.png" \285 ln -sf "$(PWD)/node_modules/yui/assets/skins/sam/rail-x.png" \
286 build-$(1)/juju-ui/assets/combined-css/rail-x.png286 build-$(1)/juju-ui/assets/combined-css/rail-x.png
287 # Link each YUI module's assets.287 ln -sf "$(PWD)/node_modules/yui/event-simulate/event-simulate.js" \
288 mkdir -p build-$(1)/juju-ui/assets/skins/night/ \288 build-$(1)/juju-ui/assets/
289 build-$(1)/juju-ui/assets/skins/sam/289 ln -sf "$(PWD)/node_modules/yui/node-event-simulate/node-event-simulate.js" \
290 find node_modules/yui/ -path "*/skins/night/*" -type f \290 build-$(1)/juju-ui/assets
291 -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/skins/night/ \;291 # Copy each YUI module's assets to a parallel directory in the build
292 find node_modules/yui/ -path "*/skins/sam/*" -type f \292 # location. This is run in a subshell (indicated by the parenthesis)
293 -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/skins/sam/ \;293 # so we can change directory and have it not effect this process. To
294 find node_modules/yui/ -path "*/assets/*" \! -path "*/skins/*" -type f \294 # understand how it does what it does look at the man page for cp,
295 -exec ln -sf "$(PWD)/{}" build-$(1)/juju-ui/assets/ \;295 # particularly "--parents". Notice that this makes copies instead of
296 # links. This goes against the way the dependencies are structured and
297 # so may be a problem in the future. If so, a way to do this as links
298 # would be called for.
299 (cd node_modules/yui/ && \
300 cp -r --parents */assets "$(PWD)/build-$(1)/juju-ui/assets/")
296endef301endef
297302
298$(LINK_DEBUG_FILES):303$(LINK_DEBUG_FILES):
@@ -319,7 +324,11 @@
319test-prod: build-prod324test-prod: build-prod
320 ./test-server.sh prod325 ./test-server.sh prod
321326
322test: test-debug327test:
328 @echo "Deprecated. Please run either 'make test-prod' or 'make"
329 @echo "test-debug', to test the production or debug environments"
330 @echo "respectively. Run 'make help' to list the main available "
331 @echo "targets."
323332
324server:333server:
325 @echo "Deprecated. Please run either 'make prod' or 'make debug',"334 @echo "Deprecated. Please run either 'make prod' or 'make debug',"
326335
=== modified file 'app/app.js'
--- app/app.js 2012-12-04 22:08:40 +0000
+++ app/app.js 2012-12-17 14:57:47 +0000
@@ -761,6 +761,7 @@
761}, '0.5.2', {761}, '0.5.2', {
762 requires: [762 requires: [
763 'juju-models',763 'juju-models',
764 'juju-charm-models',
764 'juju-views',765 'juju-views',
765 'juju-controllers',766 'juju-controllers',
766 'juju-view-charm-search',767 'juju-view-charm-search',
767768
=== 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-17 14:57:47 +0000
@@ -697,6 +697,31 @@
697 'input.config-field[type=checkbox]':697 'input.config-field[type=checkbox]':
698 {click: function(evt) {evt.target.focus();}}698 {click: function(evt) {evt.target.focus();}}
699 },699 },
700 /**
701 * Determines the Y coordinate that would center a tooltip on a field.
702 *
703 * @static
704 * @param {Number} fieldY The current Y position of the tooltip.
705 * @param {Number} fieldHeight The hight of the field.
706 * @param {Number} tooltipHeight The height of the tooltip.
707 * @return {Number} New Y coordinate for the tooltip.
708 */
709 _calculateTooltipY: function(fieldY, fieldHeight, tooltipHeight) {
710 var y_offset = (tooltipHeight - fieldHeight) / 2;
711 return fieldY - y_offset;
712 },
713 /**
714 * Determines the X coordinate that would place a tooltip next to a
715 * field.
716 *
717 * @static
718 * @param {Number} fieldX The current X position of the tooltip.
719 * @param {Number} tooltipWidth The width of the tooltip.
720 * @return {Number} New X coordinate for the tooltip.
721 */
722 _calculateTooltipX: function(fieldX, tooltipWidth) {
723 return fieldX - tooltipWidth - 15;
724 },
700 _moveTooltip: function() {725 _moveTooltip: function() {
701 if (this.tooltip.field &&726 if (this.tooltip.field &&
702 Y.DOM.inRegion(727 Y.DOM.inRegion(
@@ -708,10 +733,13 @@
708 var widget = this.tooltip.get('boundingBox'),733 var widget = this.tooltip.get('boundingBox'),
709 tooltipWidth = widget.get('clientWidth'),734 tooltipWidth = widget.get('clientWidth'),
710 tooltipHeight = widget.get('clientHeight'),735 tooltipHeight = widget.get('clientHeight'),
711 y_offset = (tooltipHeight - fieldHeight) / 2;736 fieldX = this.tooltip.panel.getX(),
712 this.tooltip.move( // These are the x, y coordinates.737 fieldY = this.tooltip.field.getY(),
713 [this.tooltip.panel.getX() - tooltipWidth - 15,738 tooltipX = this._calculateTooltipX(
714 this.tooltip.field.getY() - y_offset]);739 fieldX, tooltipWidth),
740 tooltipY = this._calculateTooltipY(
741 fieldY, fieldHeight, tooltipHeight);
742 this.tooltip.move([tooltipX, tooltipY]);
715 if (!this.tooltip.get('visible')) {743 if (!this.tooltip.get('visible')) {
716 this.tooltip.show();744 this.tooltip.show();
717 }745 }
718746
=== modified file 'bin/merge-files'
--- bin/merge-files 2012-12-14 20:39:08 +0000
+++ bin/merge-files 2012-12-17 14:57:47 +0000
@@ -48,6 +48,8 @@
4848
49 // templates.js is a generated file. It is not part of the app directory.49 // templates.js is a generated file. It is not part of the app directory.
50 paths.push(syspath.join(process.cwd(), 'build/juju-ui/templates.js'));50 paths.push(syspath.join(process.cwd(), 'build/juju-ui/templates.js'));
51 // XXX Why do we have to do this? (bug 1090563).
52 paths.push(syspath.join(process.cwd(), 'app/models/charm.js'));
5153
52 merge.combineJs(paths, 'build/juju-ui/assets/app.js');54 merge.combineJs(paths, 'build/juju-ui/assets/app.js');
5355
5456
=== modified file 'test/index.html'
--- test/index.html 2012-12-11 04:11:39 +0000
+++ test/index.html 2012-12-17 14:57:47 +0000
@@ -3,8 +3,12 @@
3<head>3<head>
4 <meta charset="utf-8">4 <meta charset="utf-8">
5 <link rel="stylesheet" href="assets/mocha.css">5 <link rel="stylesheet" href="assets/mocha.css">
6 <script src="/juju-ui/assets/modules.js"></script>
6 <script src="/juju-ui/assets/all-yui.js"></script>7 <script src="/juju-ui/assets/all-yui.js"></script>
7 <script src="/juju-ui/assets/modules.js"></script>8 <!-- Since only the tests depend on these files and the prod tests disable
9 the YUI loader, we have to include them manually here. -->
10 <script src="/juju-ui/assets/event-simulate.js"></script>
11 <script src="/juju-ui/assets/node-event-simulate.js"></script>
8 <script src="assets/chai.js"></script>12 <script src="assets/chai.js"></script>
9 <script src="assets/mocha.js"></script>13 <script src="assets/mocha.js"></script>
10 <script src="utils.js"></script>14 <script src="utils.js"></script>
1115
=== 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-17 14:57:47 +0000
@@ -24,6 +24,7 @@
24 before(function(done) {24 before(function(done) {
25 Y = YUI(GlobalConfig).use(25 Y = YUI(GlobalConfig).use(
26 'juju-models',26 'juju-models',
27 'juju-charm-models',
27 'juju-views',28 'juju-views',
28 'juju-gui',29 'juju-gui',
29 'juju-env',30 'juju-env',
@@ -199,35 +200,25 @@
199 tooltip.get('visible').should.equal(true);200 tooltip.get('visible').should.equal(true);
200 });201 });
201202
202 it('must keep the tooltip aligned with its field', function() {203 it('must keep the tooltip aligned with its field vertically', function() {
203 var charm = new models.Charm({id: 'precise/mysql-7'}),204 // The tooltip's Y coordinate should be such that it is centered vertically
204 view = new views.CharmConfigurationView(205 // on its associated field.
205 { container: container,206 var fieldHeight = 7;
206 model: charm,207 var tooltipHeight = 17;
207 tooltipDelay: 0 });208 var fieldY = 1000;
208 charm.setAttrs(charmConfig);209 var view = new views.CharmConfigurationView();
209 charm.loaded = true;210 var y = view._calculateTooltipY(fieldY, fieldHeight, tooltipHeight);
210 view.render();211 assert.equal(y, 995);
211 var tooltip = view.tooltip,212 });
212 controls = container.all('.control-group input'),213
213 panel = container.one('.charm-panel');214 it('must keep the tooltip to the left of its field', function() {
214 // The panel needs to be scrollable and smaller than what it contains. We215 // The tooltip's X coordinate should be such that it is to the left of its
215 // do this by setting a height to the panel and then setting the height to216 // associated field.
216 // one of the controls to something much bigger.217 var tooltipWidth = 100;
217 panel.setStyles({height: '400px', overflowY: 'auto'});218 var fieldX = 1000;
218 controls.item(2).set('height', '4000px');219 var view = new views.CharmConfigurationView();
219 // We need to have the field visible or else the call to "focus" will220 var x = view._calculateTooltipX(fieldX, tooltipWidth);
220 // change the positioning after our calculation has occurred, thus221 assert.equal(x, 885);
221 // changing our Y field.
222 controls.item(1).scrollIntoView();
223 controls.item(1).focus();
224 var originalY = tooltip.get('boundingBox').getY();
225 panel.set('scrollTop', panel.get('scrollTop') + 10);
226 // The simulate module does not support firing scroll events so we call
227 // the associated method directly.
228 view._moveTooltip();
229 Math.floor(tooltip.get('boundingBox').getY())
230 .should.equal(Math.floor(originalY - 10));
231 });222 });
232223
233 it('must hide the tooltip when its field scrolls away', function() {224 it('must hide the tooltip when its field scrolls away', function() {
234225
=== modified file 'test/test_model.js'
--- test/test_model.js 2012-11-23 16:21:32 +0000
+++ test/test_model.js 2012-12-17 14:57:47 +0000
@@ -1,356 +1,359 @@
1'use strict';1'use strict';
22
3YUI(GlobalConfig).use('juju-models', function(Y) {3describe('charm normalization', function() {
4 describe('charm normalization', function() {4 var models;
5 var models;5
66 before(function() {
7 before(function() {7 YUI(GlobalConfig).use('juju-models', 'juju-charm-models', function(Y) {
8 models = Y.namespace('juju.models');8 models = Y.namespace('juju.models');
9 });9 });
1010 });
11 it('must create derived attributes from official charm id', function() {11
12 var charm = new models.Charm(12 it('must create derived attributes from official charm id', function() {
13 {id: 'cs:precise/openstack-dashboard-0'});13 var charm = new models.Charm(
14 charm.get('scheme').should.equal('cs');14 {id: 'cs:precise/openstack-dashboard-0'});
15 var _ = expect(charm.get('owner')).to.not.exist;15 charm.get('scheme').should.equal('cs');
16 charm.get('full_name').should.equal('precise/openstack-dashboard');16 var _ = expect(charm.get('owner')).to.not.exist;
17 charm.get('charm_store_path').should.equal(17 charm.get('full_name').should.equal('precise/openstack-dashboard');
18 'charms/precise/openstack-dashboard-0/json');18 charm.get('charm_store_path').should.equal(
19 });19 'charms/precise/openstack-dashboard-0/json');
2020 });
21 it('must convert timestamps into time objects', function() {21
22 var time = 1349797266.032,22 it('must convert timestamps into time objects', function() {
23 date = new Date(time),23 var time = 1349797266.032,
24 charm = new models.Charm(24 date = new Date(time),
25 { id: 'cs:precise/foo-9', last_change: {created: time / 1000} });25 charm = new models.Charm(
26 charm.get('last_change').created.should.eql(date);26 { id: 'cs:precise/foo-9', last_change: {created: time / 1000} });
27 });27 charm.get('last_change').created.should.eql(date);
2828 });
29 });29
30});30});
3131
32YUI(GlobalConfig).use('juju-models', function(Y) {32describe('juju models', function() {
33 describe('juju models', function() {33 var models;
34 var models;34
3535 before(function(done) {
36 before(function() {36 YUI(GlobalConfig).use('juju-models', 'juju-charm-models', function(Y) {
37 models = Y.namespace('juju.models');37 models = Y.namespace('juju.models');
38 });38 done();
3939 });
40 it('must be able to create charm', function() {40 });
41 var charm = new models.Charm(41
42 {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});42 it('must be able to create charm', function() {
43 charm.get('scheme').should.equal('cs');43 var charm = new models.Charm(
44 charm.get('owner').should.equal('alt-bac');44 {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
45 charm.get('series').should.equal('precise');45 charm.get('scheme').should.equal('cs');
46 charm.get('package_name').should.equal('openstack-dashboard');46 charm.get('owner').should.equal('alt-bac');
47 charm.get('revision').should.equal(0);47 charm.get('series').should.equal('precise');
48 charm.get('full_name').should.equal(48 charm.get('package_name').should.equal('openstack-dashboard');
49 '~alt-bac/precise/openstack-dashboard');49 charm.get('revision').should.equal(0);
50 charm.get('charm_store_path').should.equal(50 charm.get('full_name').should.equal(
51 '~alt-bac/precise/openstack-dashboard-0/json');51 '~alt-bac/precise/openstack-dashboard');
52 });52 charm.get('charm_store_path').should.equal(
5353 '~alt-bac/precise/openstack-dashboard-0/json');
54 it('must be able to parse real-world charm names', function() {54 });
55 var charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});55
56 charm.get('full_name').should.equal('precise/openstack-dashboard');56 it('must be able to parse real-world charm names', function() {
57 charm.get('package_name').should.equal('openstack-dashboard');57 var charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});
58 charm.get('charm_store_path').should.equal(58 charm.get('full_name').should.equal('precise/openstack-dashboard');
59 'charms/precise/openstack-dashboard-0/json');59 charm.get('package_name').should.equal('openstack-dashboard');
60 charm.get('scheme').should.equal('cs');60 charm.get('charm_store_path').should.equal(
61 var _ = expect(charm.get('owner')).to.not.exist;61 'charms/precise/openstack-dashboard-0/json');
62 charm.get('series').should.equal('precise');62 charm.get('scheme').should.equal('cs');
63 charm.get('package_name').should.equal('openstack-dashboard');63 var _ = expect(charm.get('owner')).to.not.exist;
64 charm.get('revision').should.equal(0);64 charm.get('series').should.equal('precise');
65 });65 charm.get('package_name').should.equal('openstack-dashboard');
6666 charm.get('revision').should.equal(0);
67 it('must be able to parse individually owned charms', function() {67 });
68 // Note that an earlier version of the parsing code did not handle68
69 // hyphens in user names, so this test intentionally includes one.69 it('must be able to parse individually owned charms', function() {
70 var charm = new models.Charm(70 // Note that an earlier version of the parsing code did not handle
71 {id: 'cs:~marco-ceppi/precise/wordpress-17'});71 // hyphens in user names, so this test intentionally includes one.
72 charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');72 var charm = new models.Charm(
73 charm.get('package_name').should.equal('wordpress');73 {id: 'cs:~marco-ceppi/precise/wordpress-17'});
74 charm.get('charm_store_path').should.equal(74 charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
75 '~marco-ceppi/precise/wordpress-17/json');75 charm.get('package_name').should.equal('wordpress');
76 charm.get('revision').should.equal(17);76 charm.get('charm_store_path').should.equal(
77 });77 '~marco-ceppi/precise/wordpress-17/json');
7878 charm.get('revision').should.equal(17);
79 it('must reject bad charm ids.', function() {79 });
80 try {80
81 var charm = new models.Charm({id: 'foobar'});81 it('must reject bad charm ids.', function() {
82 assert.fail('Should have thrown an error');82 try {
83 } catch (e) {83 var charm = new models.Charm({id: 'foobar'});
84 e.should.equal(84 assert.fail('Should have thrown an error');
85 'Developers must initialize charms with a well-formed id.');85 } catch (e) {
86 }86 e.should.equal(
87 });87 'Developers must initialize charms with a well-formed id.');
8888 }
89 it('must reject missing charm ids at initialization.', function() {89 });
90 try {90
91 var charm = new models.Charm();91 it('must reject missing charm ids at initialization.', function() {
92 assert.fail('Should have thrown an error');92 try {
93 } catch (e) {93 var charm = new models.Charm();
94 e.should.equal(94 assert.fail('Should have thrown an error');
95 'Developers must initialize charms with a well-formed id.');95 } catch (e) {
96 }96 e.should.equal(
97 });97 'Developers must initialize charms with a well-formed id.');
9898 }
99 it('must be able to create charm list', function() {99 });
100 var c1 = new models.Charm(100
101 { id: 'cs:precise/mysql-2',101 it('must be able to create charm list', function() {
102 description: 'A DB'}),102 var c1 = new models.Charm(
103 c2 = new models.Charm(103 { id: 'cs:precise/mysql-2',
104 { id: 'cs:precise/logger-3',104 description: 'A DB'}),
105 description: 'Log sub'}),105 c2 = new models.Charm(
106 clist = new models.CharmList();106 { id: 'cs:precise/logger-3',
107 clist.add([c1, c2]);107 description: 'Log sub'}),
108 var names = clist.map(function(c) {return c.get('package_name');});108 clist = new models.CharmList();
109 names[0].should.equal('mysql');109 clist.add([c1, c2]);
110 names[1].should.equal('logger');110 var names = clist.map(function(c) {return c.get('package_name');});
111 });111 names[0].should.equal('mysql');
112112 names[1].should.equal('logger');
113113 });
114 it('service unit list should be able to get units of a given service',114
115 function() {115
116 var sl = new models.ServiceList();116 it('service unit list should be able to get units of a given service',
117 var sul = new models.ServiceUnitList();117 function() {
118 var mysql = new models.Service({id: 'mysql'});118 var sl = new models.ServiceList();
119 var wordpress = new models.Service({id: 'wordpress'});119 var sul = new models.ServiceUnitList();
120 sl.add([mysql, wordpress]);120 var mysql = new models.Service({id: 'mysql'});
121 sl.getById('mysql').should.equal(mysql);121 var wordpress = new models.Service({id: 'wordpress'});
122 sl.getById('wordpress').should.equal(wordpress);122 sl.add([mysql, wordpress]);
123123 sl.getById('mysql').should.equal(mysql);
124 sul.add([{id: 'mysql/0'}, {id: 'mysql/1'}]);124 sl.getById('wordpress').should.equal(wordpress);
125125
126 var wp0 = {id: 'wordpress/0'},126 sul.add([{id: 'mysql/0'}, {id: 'mysql/1'}]);
127 wp1 = {id: 'wordpress/1'};127
128 sul.add([wp0, wp1]);128 var wp0 = {id: 'wordpress/0'};
129 wp0.service.should.equal('wordpress');129 var wp1 = {id: 'wordpress/1'};
130130 sul.add([wp0, wp1]);
131 sul.get_units_for_service(mysql, true).getAttrs(['id']).id.should.eql(131 wp0.service.should.equal('wordpress');
132 ['mysql/0', 'mysql/1']);132
133 sul.get_units_for_service(wordpress, true).getAttrs(133 sul.get_units_for_service(mysql, true).getAttrs(['id']).id.should.eql(
134 ['id']).id.should.eql(['wordpress/0', 'wordpress/1']);134 ['mysql/0', 'mysql/1']);
135 });135 sul.get_units_for_service(wordpress, true).getAttrs(
136136 ['id']).id.should.eql(['wordpress/0', 'wordpress/1']);
137 it('service unit list should be able to aggregate unit statuses',137 });
138 function() {138
139 var sl = new models.ServiceList();139 it('service unit list should be able to aggregate unit statuses',
140 var sul = new models.ServiceUnitList();140 function() {
141 var mysql = new models.Service({id: 'mysql'});141 var sl = new models.ServiceList();
142 var wordpress = new models.Service({id: 'wordpress'});142 var sul = new models.ServiceUnitList();
143 sl.add([mysql, wordpress]);143 var mysql = new models.Service({id: 'mysql'});
144144 var wordpress = new models.Service({id: 'wordpress'});
145 var my0 = new models.ServiceUnit(145 sl.add([mysql, wordpress]);
146 {id: 'mysql/0', agent_state: 'pending'}),146
147 my1 = new models.ServiceUnit(147 var my0 = new models.ServiceUnit({
148 {id: 'mysql/1', agent_state: 'pending'});148 id: 'mysql/0',
149149 agent_state: 'pending'});
150 sul.add([my0, my1]);150 var my1 = new models.ServiceUnit({
151151 id: 'mysql/1',
152 var wp0 = new models.ServiceUnit(152 agent_state: 'pending'});
153 { id: 'wordpress/0',153
154 agent_state: 'pending'}),154 sul.add([my0, my1]);
155 wp1 = new models.ServiceUnit(155
156 { id: 'wordpress/1',156 var wp0 = new models.ServiceUnit({
157 agent_state: 'error'});157 id: 'wordpress/0',
158 sul.add([wp0, wp1]);158 agent_state: 'pending'});
159159 var wp1 = new models.ServiceUnit({
160 sul.get_informative_states_for_service(mysql).should.eql(160 id: 'wordpress/1',
161 {'pending': 2});161 agent_state: 'error'});
162 sul.get_informative_states_for_service(wordpress).should.eql(162 sul.add([wp0, wp1]);
163 {'pending': 1, 'error': 1});163
164 });164 sul.get_informative_states_for_service(mysql).should.eql(
165165 {'pending': 2});
166 it('service unit objects should parse the service name from unit id',166 sul.get_informative_states_for_service(wordpress).should.eql(
167 function() {167 {'pending': 1, 'error': 1});
168 var service_unit = {id: 'mysql/0'},168 });
169 db = new models.Database();169
170 db.units.add(service_unit);170 it('service unit objects should parse the service name from unit id',
171 service_unit.service.should.equal('mysql');171 function() {
172 });172 var service_unit = {id: 'mysql/0'};
173173 var db = new models.Database();
174 it('service unit objects should report their number correctly',174 db.units.add(service_unit);
175 function() {175 service_unit.service.should.equal('mysql');
176 var service_unit = {id: 'mysql/5'},176 });
177 db = new models.Database();177
178 db.units.add(service_unit);178 it('service unit objects should report their number correctly',
179 service_unit.number.should.equal(5);179 function() {
180 });180 var service_unit = {id: 'mysql/5'};
181181 var db = new models.Database();
182 it('must be able to resolve models by modelId', function() {182 db.units.add(service_unit);
183 var db = new models.Database();183 service_unit.number.should.equal(5);
184184 });
185 db.services.add([{id: 'wordpress'}, {id: 'mediawiki'}]);185
186 db.units.add([{id: 'wordpress/0'}, {id: 'wordpress/1'}]);186 it('must be able to resolve models by modelId', function() {
187187 var db = new models.Database();
188 var model = db.services.item(0);188
189 // Single parameter calling189 db.services.add([{id: 'wordpress'}, {id: 'mediawiki'}]);
190 db.getModelById([model.name, model.get('id')])190 db.units.add([{id: 'wordpress/0'}, {id: 'wordpress/1'}]);
191 .get('id').should.equal('wordpress');191
192 // Two parameter interface192 var model = db.services.item(0);
193 db.getModelById(model.name, model.get('id'))193 // Single parameter calling
194 .get('id').should.equal('wordpress');194 db.getModelById([model.name, model.get('id')])
195195 .get('id').should.equal('wordpress');
196 var unit = db.units.item(0);196 // Two parameter interface
197 db.getModelById([unit.name, unit.id]).id.should.equal('wordpress/0');197 db.getModelById(model.name, model.get('id'))
198 db.getModelById(unit.name, unit.id).id.should.equal('wordpress/0');198 .get('id').should.equal('wordpress');
199 });199
200200 var unit = db.units.item(0);
201 it('on_delta should handle remove changes correctly',201 db.getModelById([unit.name, unit.id]).id.should.equal('wordpress/0');
202 function() {202 db.getModelById(unit.name, unit.id).id.should.equal('wordpress/0');
203 var db = new models.Database();203 });
204 var my0 = new models.ServiceUnit({id: 'mysql/0',204
205 agent_state: 'pending'}),205 it('on_delta should handle remove changes correctly',
206 my1 = new models.ServiceUnit({id: 'mysql/1',206 function() {
207 agent_state: 'pending'});207 var db = new models.Database();
208 db.units.add([my0, my1]);208 var my0 = new models.ServiceUnit({id: 'mysql/0',
209 db.on_delta({data: {result: [209 agent_state: 'pending'});
210 ['unit', 'remove', 'mysql/1']210 var my1 = new models.ServiceUnit({id: 'mysql/1',
211 ]}});211 agent_state: 'pending'});
212 var names = db.units.get('id');212 db.units.add([my0, my1]);
213 names.length.should.equal(1);213 db.on_delta({data: {result: [
214 names[0].should.equal('mysql/0');214 ['unit', 'remove', 'mysql/1']
215 });215 ]}});
216216 var names = db.units.get('id');
217 it('on_delta should be able to reuse existing services with add',217 names.length.should.equal(1);
218 function() {218 names[0].should.equal('mysql/0');
219 var db = new models.Database();219 });
220 var my0 = new models.Service({id: 'mysql', exposed: true});220
221 db.services.add([my0]);221 it('on_delta should be able to reuse existing services with add',
222 // Note that exposed is not set explicitly to false.222 function() {
223 db.on_delta({data: {result: [223 var db = new models.Database();
224 ['service', 'add', {id: 'mysql'}]224 var my0 = new models.Service({id: 'mysql', exposed: true});
225 ]}});225 db.services.add([my0]);
226 my0.get('exposed').should.equal(false);226 // Note that exposed is not set explicitly to false.
227 });227 db.on_delta({data: {result: [
228228 ['service', 'add', {id: 'mysql'}]
229 it('on_delta should be able to reuse existing units with add',229 ]}});
230 // Units are special because they use the LazyModelList.230 my0.get('exposed').should.equal(false);
231 function() {231 });
232 var db = new models.Database();232
233 var my0 = {id: 'mysql/0', agent_state: 'pending'};233 it('on_delta should be able to reuse existing units with add',
234 db.units.add([my0]);234 // Units are special because they use the LazyModelList.
235 db.on_delta({data: {result: [235 function() {
236 ['unit', 'add', {id: 'mysql/0', agent_state: 'another'}]236 var db = new models.Database();
237 ]}});237 var my0 = {id: 'mysql/0', agent_state: 'pending'};
238 my0.agent_state.should.equal('another');238 db.units.add([my0]);
239 });239 db.on_delta({data: {result: [
240240 ['unit', 'add', {id: 'mysql/0', agent_state: 'another'}]
241 it('on_delta should reset relation_errors',241 ]}});
242 function() {242 my0.agent_state.should.equal('another');
243 var db = new models.Database();243 });
244 var my0 = {id: 'mysql/0', relation_errors: {'cache': ['memcached']}};244
245 db.units.add([my0]);245 it('on_delta should reset relation_errors',
246 // Note that relation_errors is not set.246 function() {
247 db.on_delta({data: {result: [247 var db = new models.Database();
248 ['unit', 'change', {id: 'mysql/0'}]248 var my0 = {id: 'mysql/0', relation_errors: {'cache': ['memcached']}};
249 ]}});249 db.units.add([my0]);
250 my0.relation_errors.should.eql({});250 // Note that relation_errors is not set.
251 });251 db.on_delta({data: {result: [
252252 ['unit', 'change', {id: 'mysql/0'}]
253 it('ServiceUnitList should accept a list of units at instantiation and ' +253 ]}});
254 'decorate them', function() {254 my0.relation_errors.should.eql({});
255 var mysql = new models.Service({id: 'mysql'});255 });
256 var objs = [{id: 'mysql/0'},256
257 {id: 'mysql/1'}];257 it('ServiceUnitList should accept a list of units at instantiation and ' +
258 var sul = new models.ServiceUnitList({items: objs});258 'decorate them', function() {
259 var unit_data = sul.get_units_for_service(259 var mysql = new models.Service({id: 'mysql'});
260 mysql, true).getAttrs(['service', 'number']);260 var objs = [{id: 'mysql/0'},
261 unit_data.service.should.eql(['mysql', 'mysql']);261 {id: 'mysql/1'}];
262 unit_data.number.should.eql([0, 1]);262 var sul = new models.ServiceUnitList({items: objs});
263 });263 var unit_data = sul.get_units_for_service(
264264 mysql, true).getAttrs(['service', 'number']);
265 it('RelationList.has_relations.. should return true if rel found.',265 unit_data.service.should.eql(['mysql', 'mysql']);
266 function() {266 unit_data.number.should.eql([0, 1]);
267 var db = new models.Database(),267 });
268 service = new models.Service({id: 'mysql', exposed: false}),268
269 rel0 = new models.Relation({269 it('RelationList.has_relations.. should return true if rel found.',
270 id: 'relation-0',270 function() {
271 endpoints: [271 var db = new models.Database(),
272 ['mediawiki', {name: 'cache', role: 'source'}],272 service = new models.Service({id: 'mysql', exposed: false}),
273 ['squid', {name: 'cache', role: 'front'}]],273 rel0 = new models.Relation({
274 'interface': 'cache'274 id: 'relation-0',
275 }),275 endpoints: [
276 rel1 = new models.Relation({276 ['mediawiki', {name: 'cache', role: 'source'}],
277 id: 'relation-4',277 ['squid', {name: 'cache', role: 'front'}]],
278 endpoints: [278 'interface': 'cache'
279 ['something', {name: 'foo', role: 'bar'}],279 }),
280 ['mysql', {name: 'la', role: 'lee'}]],280 rel1 = new models.Relation({
281 'interface': 'thing'281 id: 'relation-4',
282 });282 endpoints: [
283 db.relations.add([rel0, rel1]);283 ['something', {name: 'foo', role: 'bar'}],
284 db.relations.has_relation_for_endpoint(284 ['mysql', {name: 'la', role: 'lee'}]],
285 {service: 'squid', name: 'cache', type: 'cache'}285 'interface': 'thing'
286 ).should.equal(true);286 });
287 db.relations.has_relation_for_endpoint(287 db.relations.add([rel0, rel1]);
288 {service: 'mysql', name: 'la', type: 'thing'}288 db.relations.has_relation_for_endpoint(
289 ).should.equal(true);289 {service: 'squid', name: 'cache', type: 'cache'}
290 db.relations.has_relation_for_endpoint(290 ).should.equal(true);
291 {service: 'squid', name: 'cache', type: 'http'}291 db.relations.has_relation_for_endpoint(
292 ).should.equal(false);292 {service: 'mysql', name: 'la', type: 'thing'}
293293 ).should.equal(true);
294 // We can also pass a service name which must match for the294 db.relations.has_relation_for_endpoint(
295 // same relation.295 {service: 'squid', name: 'cache', type: 'http'}
296296 ).should.equal(false);
297 db.relations.has_relation_for_endpoint(297
298 {service: 'squid', name: 'cache', type: 'cache'},298 // We can also pass a service name which must match for the
299 'kafka'299 // same relation.
300 ).should.equal(false);300
301301 db.relations.has_relation_for_endpoint(
302 db.relations.has_relation_for_endpoint(302 {service: 'squid', name: 'cache', type: 'cache'},
303 {service: 'squid', name: 'cache', type: 'cache'},303 'kafka'
304 'mediawiki'304 ).should.equal(false);
305 ).should.equal(true);305
306306 db.relations.has_relation_for_endpoint(
307 });307 {service: 'squid', name: 'cache', type: 'cache'},
308308 'mediawiki'
309 it('RelationList.get_relations_for_service should do what it says',309 ).should.equal(true);
310 function() {310
311 var db = new models.Database(),311 });
312 service = new models.Service({id: 'mysql', exposed: false}),312
313 rel0 = new models.Relation(313 it('RelationList.get_relations_for_service should do what it says',
314 { id: 'relation-0',314 function() {
315 endpoints:315 var db = new models.Database(),
316 [['mediawiki', {name: 'cache', role: 'source'}],316 service = new models.Service({id: 'mysql', exposed: false}),
317 ['squid', {name: 'cache', role: 'front'}]],317 rel0 = new models.Relation(
318 'interface': 'cache'318 { id: 'relation-0',
319 }),319 endpoints:
320 rel1 = new models.Relation(320 [['mediawiki', {name: 'cache', role: 'source'}],
321 { id: 'relation-1',321 ['squid', {name: 'cache', role: 'front'}]],
322 endpoints:322 'interface': 'cache'
323 [['wordpress', {role: 'peer', name: 'loadbalancer'}]],323 }),
324 'interface': 'reversenginx'324 rel1 = new models.Relation(
325 }),325 { id: 'relation-1',
326 rel2 = new models.Relation(326 endpoints:
327 { id: 'relation-2',327 [['wordpress', {role: 'peer', name: 'loadbalancer'}]],
328 endpoints:328 'interface': 'reversenginx'
329 [['mysql', {name: 'db', role: 'db'}],329 }),
330 ['mediawiki', {name: 'storage', role: 'app'}]],330 rel2 = new models.Relation(
331 'interface': 'db'331 { id: 'relation-2',
332 }),332 endpoints:
333 rel3 = new models.Relation(333 [['mysql', {name: 'db', role: 'db'}],
334 { id: 'relation-3',334 ['mediawiki', {name: 'storage', role: 'app'}]],
335 endpoints:335 'interface': 'db'
336 [['mysql', {role: 'peer', name: 'loadbalancer'}]],336 }),
337 'interface': 'mysql-loadbalancer'337 rel3 = new models.Relation(
338 }),338 { id: 'relation-3',
339 rel4 = new models.Relation(339 endpoints:
340 { id: 'relation-4',340 [['mysql', {role: 'peer', name: 'loadbalancer'}]],
341 endpoints:341 'interface': 'mysql-loadbalancer'
342 [['something', {name: 'foo', role: 'bar'}],342 }),
343 ['mysql', {name: 'la', role: 'lee'}]],343 rel4 = new models.Relation(
344 'interface': 'thing'344 { id: 'relation-4',
345 });345 endpoints:
346 db.relations.add([rel0, rel1, rel2, rel3, rel4]);346 [['something', {name: 'foo', role: 'bar'}],
347 Y.Array.map(347 ['mysql', {name: 'la', role: 'lee'}]],
348 db.relations.get_relations_for_service(service),348 'interface': 'thing'
349 function(r) { return r.get('id'); })349 });
350 .should.eql(['relation-2', 'relation-3', 'relation-4']);350 db.relations.add([rel0, rel1, rel2, rel3, rel4]);
351 });351 db.relations.get_relations_for_service(service).map(
352 });352 function(r) { return r.get('id'); })
353});353 .should.eql(['relation-2', 'relation-3', 'relation-4']);
354 });
355});
356
354357
355YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',358YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
356 'juju-tests-utils', 'json-stringify',359 'juju-tests-utils', 'json-stringify',

Subscribers

People subscribed via source and target branches