apps 0.8.2 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +51 -166
  2. package/dist/authoring/index.d.ts +205 -0
  3. package/dist/authoring/index.js +26 -0
  4. package/dist/index-8wmxmfhc.js +90 -0
  5. package/dist/index-dh4z81es.js +10 -0
  6. package/dist/index-mcjwntr2.js +13 -0
  7. package/dist/index-wfngzvv2.js +228 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.js +26 -0
  10. package/dist/manifest/collect.d.ts +64 -0
  11. package/dist/manifest/contract.d.ts +55 -0
  12. package/dist/manifest/contract.js +8 -0
  13. package/dist/manifest/index.d.ts +18 -0
  14. package/dist/manifest/index.js +15 -0
  15. package/dist/runner/index.d.ts +4 -0
  16. package/dist/runner/index.js +61 -0
  17. package/dist/runner/protocol.d.ts +28 -0
  18. package/dist/runner/protocol.js +6 -0
  19. package/dist/workerd/index.d.ts +2 -0
  20. package/dist/workerd/index.js +3 -0
  21. package/package.json +49 -36
  22. package/src/authoring/index.ts +340 -0
  23. package/src/index.ts +9 -0
  24. package/src/manifest/collect.ts +198 -0
  25. package/src/manifest/contract.ts +55 -0
  26. package/src/manifest/index.ts +224 -0
  27. package/src/runner/index.ts +117 -0
  28. package/src/runner/protocol.ts +41 -0
  29. package/src/workerd/index.ts +2 -0
  30. package/.npmignore +0 -1
  31. package/bin/apps +0 -36
  32. package/bin/build +0 -14
  33. package/bin/doc +0 -16
  34. package/dist/app.js +0 -9773
  35. package/dist/app.min.js +0 -5
  36. package/dist/schema.html +0 -119
  37. package/dist/schema.md +0 -156
  38. package/index.html +0 -6
  39. package/index.js +0 -115
  40. package/lib/addons.js +0 -67
  41. package/schema.js +0 -100
  42. package/templates/app.mustache.html +0 -36
  43. package/templates/build.mustache.html +0 -13
  44. package/templates/schema.mustache.html +0 -20
  45. package/test/addons.js +0 -172
  46. package/test/cli.js +0 -53
  47. package/test/fixtures/app.json +0 -32
  48. package/test/index.js +0 -277
  49. package/test/mocha.opts +0 -2
package/dist/schema.md DELETED
@@ -1,156 +0,0 @@
1
- `app.json` is a manifest format for describing web apps. It declares environment
2
- variables, addons, and other information required to run apps on Heroku. This document describes the schema in detail.
3
-
4
- ## Example app.json
5
-
6
- ```json
7
- {
8
- "name": "small-sharp-tool",
9
- "description": "This app does one little thing, and does it well.",
10
- "keywords": [
11
- "productivity",
12
- "HTML5",
13
- "scalpel"
14
- ],
15
- "website": "https://jane-doe.github.io/small-sharp-tool",
16
- "repository": "https://github.com/jane-doe/small-sharp-tool",
17
- "logo": "https://jane-doe.github.io/small-sharp-tool/logo.svg",
18
- "success_url": "/welcome",
19
- "scripts": {
20
- "postdeploy": "bundle exec rake bootstrap"
21
- },
22
- "env": {
23
- "BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
24
- "SECRET_TOKEN": {
25
- "description": "A secret key for verifying the integrity of signed cookies.",
26
- "generator": "secret"
27
- },
28
- "WEB_CONCURRENCY": {
29
- "description": "The number of processes to run.",
30
- "default": "5"
31
- }
32
- },
33
- "addons": [
34
- "openredis",
35
- "mongolab:shared-single-small"
36
- ]
37
- }
38
- ```
39
-
40
- ## The Schema
41
-
42
- ### name
43
-
44
- A URL-friendly string that uniquely identifies the template app. *required string*
45
-
46
- ```json
47
- {
48
- "name": "small-sharp-tool"
49
- }
50
- ```
51
- ### description
52
-
53
- A brief summary of the app: what it does, who it's for, why it exists, etc. *optional string*
54
-
55
- ```json
56
- {
57
- "description": "This app does one little thing, and does it well."
58
- }
59
- ```
60
- ### keywords
61
-
62
- An array of strings describing the app. *optional array*
63
-
64
- ```json
65
- {
66
- "keywords": [
67
- "productivity",
68
- "HTML5",
69
- "scalpel"
70
- ]
71
- }
72
- ```
73
- ### website
74
-
75
- The project's website, if there is one. *optional string*
76
-
77
- ```json
78
- {
79
- "website": "https://jane-doe.github.io/small-sharp-tool"
80
- }
81
- ```
82
- ### repository
83
-
84
- The location of the application's source code. Can be a git URL, a GitHub URL, or a tarball URL. *optional string*
85
-
86
- ```json
87
- {
88
- "repository": "https://github.com/jane-doe/small-sharp-tool"
89
- }
90
- ```
91
- ### logo
92
-
93
- The URL of the application's logo image. It's dimensions should be square. Format can be SVG or PNG. *optional string*
94
-
95
- ```json
96
- {
97
- "logo": "https://jane-doe.github.io/small-sharp-tool/logo.svg"
98
- }
99
- ```
100
- ### success_url
101
-
102
- A URL specifying where to redirect the user once their new app is deployed. If value is a fully-qualified URL, the user should be redirected to that URL. If value is begins with a slash `/`, the user should be redirected to that path in their newly deployed app. *optional string*
103
-
104
- ```json
105
- {
106
- "success_url": "/welcome"
107
- }
108
- ```
109
- ### scripts
110
-
111
- A key-value object specifying scripts or shell commands to execute at different stages in the build/release process. *optional object*
112
-
113
- ```json
114
- {
115
- "scripts": {
116
- "postdeploy": "bundle exec rake bootstrap"
117
- }
118
- }
119
- ```
120
- ### env
121
-
122
- A key-value object for environment variables, or config vars in Heroku parlance. Keys are the names of the environment variables.
123
-
124
- Values can be strings or objects. If the value is a string, it will be used and the user will not be prompted to specify a different value. If the value is an object, it defines specific requirements for that variable:
125
-
126
- description - a human-friendly blurb about what the value is for and how to determine what it should be
127
- value - a default value to use
128
- generator - a string representing a function to call to generate the value, such as cookie secret *optional object*
129
-
130
- ```json
131
- {
132
- "env": {
133
- "BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
134
- "SECRET_TOKEN": {
135
- "description": "A secret key for verifying the integrity of signed cookies.",
136
- "generator": "secret"
137
- },
138
- "WEB_CONCURRENCY": {
139
- "description": "The number of processes to run.",
140
- "default": "5"
141
- }
142
- }
143
- }
144
- ```
145
- ### addons
146
-
147
- An array of strings specifying Heroku addons to provision on the app before deploying. Each addon should be in the format `addon:plan`. If plan is omitted, that addon's default plan will be provisioned. *optional array*
148
-
149
- ```json
150
- {
151
- "addons": [
152
- "openredis",
153
- "mongolab:shared-single-small"
154
- ]
155
- }
156
- ```
package/index.html DELETED
@@ -1,6 +0,0 @@
1
- <html>
2
- <head>
3
- <script type="text/javascript" charset="utf-8" src="web-post.js"></script>
4
- </head>
5
- <body></body>
6
- </html>
package/index.js DELETED
@@ -1,115 +0,0 @@
1
- 'use strict'
2
- var fs = require("fs")
3
- var hogan = require("hogan.js")
4
- var http = require('http')
5
- var superagent = require('superagent')
6
- var revalidator = require('revalidator')
7
- var parseGithubURL = require("github-url-to-object")
8
- var addons = require("./lib/addons")
9
- var schema = require("./schema")
10
-
11
- var App = module.exports = (function() {
12
-
13
- function App(raw) {
14
- var key
15
-
16
- if (typeof(raw) === 'string' && raw.match(/\.json$/i)) {
17
- raw = JSON.parse(fs.readFileSync(raw))
18
- } else if (typeof(raw) === 'string') {
19
- raw = JSON.parse(raw)
20
- }
21
-
22
- for (key in raw) {
23
- if (raw.hasOwnProperty(key)) {
24
- this[key] = raw[key]
25
- }
26
- }
27
-
28
- this.__defineGetter__("valid", function(){
29
- return this.validate().valid
30
- })
31
-
32
- this.__defineGetter__("errors", function(){
33
- return this.validate().errors
34
- })
35
-
36
- return this
37
- }
38
-
39
- App.prototype.validate = function() {
40
- return revalidator.validate(this, schema)
41
- }
42
-
43
- App.prototype.toJSON = function() {
44
- var key
45
- var out = {}
46
- var validProps = Object.keys(schema.properties)
47
- for (key in this) {
48
- if (this.hasOwnProperty(key) && validProps.indexOf(key) > -1) {
49
- out[key] = this[key]
50
- }
51
- }
52
- return JSON.stringify(out, null, 2)
53
- }
54
-
55
- App.prototype.getAddonPrices = function(cb) {
56
- var _this = this
57
- App.addons.getPrices(this.addons, function(err, prices){
58
- if (err) return cb(err)
59
- _this.prices = prices
60
- cb(null, prices)
61
- })
62
- }
63
-
64
- App.new = function(raw) {
65
- return new App(raw)
66
- }
67
-
68
- App.fetch = function(url, cb) {
69
- if (!parseGithubURL(url))
70
- return cb("Not a valid github URL: " + url)
71
-
72
- var user = parseGithubURL(url).user
73
- var repo = parseGithubURL(url).repo
74
- var proxy_url = "https://github-raw-cors-proxy.herokuapp.com/" + user + "/" + repo + "/blob/master/app.json"
75
-
76
- superagent.get(proxy_url, function(res){
77
- cb(null, App.new(res.body))
78
- })
79
- }
80
-
81
- // Hogan Templates FTW
82
- App.templates = {}
83
- // // fs.readdirSync('./templates').forEach(function(filename){
84
- // var list = ['app.mustache', 'build.mustache', 'schema.mustache']
85
- // list.forEach(function(filename){
86
- // var name = filename.replace(/\.\w+$/, '')
87
- //
88
- // // Server vs Browser
89
- // if (module.parent) {
90
- // console.log('server')
91
- // App.templates[name] = hogan.compile(fs.readFileSync('./templates/' + filename).toString())
92
- // } else {
93
- // console.log('browser')
94
- // App.templates[name] = require('.templates/'+filename)
95
- // }
96
- // })
97
-
98
- if (module.parent) {
99
- App.templates.app = hogan.compile(fs.readFileSync(__dirname + '/templates/app.mustache.html').toString())
100
- App.templates.build = hogan.compile(fs.readFileSync(__dirname + '/templates/build.mustache.html').toString())
101
- App.templates.schema = hogan.compile(fs.readFileSync(__dirname + '/templates/schema.mustache.html').toString())
102
- } else {
103
- App.templates.app = require('./templates/app.mustache.html')
104
- App.templates.build = require('./templates/build.mustache.html')
105
- App.templates.schema = require('./templates/schema.mustache.html')
106
- }
107
-
108
- App.example = new App(schema.example)
109
-
110
- App.addons = addons
111
- App.schema = schema
112
-
113
- return App
114
-
115
- })()
package/lib/addons.js DELETED
@@ -1,67 +0,0 @@
1
- 'use strict'
2
- var async = require('async')
3
- var superagent = require('superagent')
4
- var addons = module.exports = {}
5
-
6
- addons.getPrices = function(slugs, cb) {
7
-
8
- // Assemble an empty API response
9
- if (!slugs || slugs === []) {
10
- return cb(null, {
11
- plans: [],
12
- totalPrice: "Free",
13
- totalPriceInCents: 0
14
- })
15
- }
16
-
17
- async.map(slugs, addons.getPlan, function(err, plans) {
18
- if (err) return cb(err)
19
- var prices = {}
20
- prices.totalPriceInCents = plans.reduce(function(sum, plan) {
21
- return plan.price.cents + sum
22
- }, 0)
23
- prices.totalPrice = formatPrice(prices.totalPriceInCents)
24
- prices.plans = plans
25
- cb(null, prices)
26
- })
27
- }
28
-
29
- addons.getPlan = function(slug, cb) {
30
-
31
- if (slug.match(/:/)) {
32
- // format is 'addon:plan'
33
- var addon = slug.split(":")[0]
34
- var plan = slug.split(":")[1]
35
-
36
- superagent
37
- .get('https://api.heroku.com/addon-services/'+addon+'/plans/'+plan)
38
- .set('Accept', 'application/vnd.heroku+json; version=3')
39
- .end(function(err, res){
40
- if (err) return cb(err)
41
- if (res.statusCode == 404) return cb(res.body)
42
- var plan = res.body
43
- plan.prettyPrice = formatPrice(plan.price.cents)
44
- plan.logo = "https://addons.heroku.com/addons/" + addon + "/icons/original.png"
45
- cb(null, plan)
46
- })
47
-
48
- } else {
49
- // plan not specified; find the default plan
50
- superagent
51
- .get('https://api.heroku.com/addon-services/'+slug+'/plans')
52
- .set('Accept', 'application/vnd.heroku+json; version=3')
53
- .end(function(err, res){
54
- if (err) return cb(err)
55
- if (res.statusCode == 404) return cb(res.body)
56
- var plan = res.body.filter(function(plan) { return plan.default })[0]
57
- plan.prettyPrice = formatPrice(plan.price.cents)
58
- plan.logo = "https://addons.heroku.com/addons/" + slug + "/icons/original.png"
59
- cb(null, plan)
60
- })
61
- }
62
-
63
- }
64
-
65
- function formatPrice(price) {
66
- return (price == 0) ? "Free" : "$" + price/100 + "/mo"
67
- }
package/schema.js DELETED
@@ -1,100 +0,0 @@
1
- 'use strict'
2
- var schema = {
3
- "properties": {
4
- "name": {
5
- "description": "A URL-friendly string that uniquely identifies the template app.",
6
- "type": "string",
7
- "required": true,
8
- "allowEmpty": false,
9
- "example": "small-sharp-tool"
10
- },
11
- "description": {
12
- "description": "A brief summary of the app: what it does, who it's for, why it exists, etc.",
13
- "type": "string",
14
- "example": "This app does one little thing, and does it well."
15
- },
16
- "keywords": {
17
- "description": "An array of strings describing the app.",
18
- "type": "array",
19
- "example": ["productivity", "HTML5", "scalpel"]
20
- },
21
- "website": {
22
- "description": "The project's website, if there is one.",
23
- "type": "string",
24
- "format": "url",
25
- "allowEmpty": false,
26
- "example": "https://jane-doe.github.io/small-sharp-tool"
27
- },
28
- "repository": {
29
- "description": "The location of the application's source code. Can be a git URL, a GitHub URL, or a tarball URL.",
30
- "type": "string",
31
- "format": "url",
32
- "allowEmpty": false,
33
- "example": "https://github.com/jane-doe/small-sharp-tool"
34
- },
35
- "logo": {
36
- "description": "The URL of the application's logo image. It's dimensions should be square. Format can be SVG or PNG.",
37
- "type": "string",
38
- "format": "url",
39
- "allowEmpty": false,
40
- "example": "https://jane-doe.github.io/small-sharp-tool/logo.svg"
41
- },
42
- "success_url": {
43
- "description": "A URL specifying where to redirect the user once their new app is deployed. If value is a fully-qualified URL, the user should be redirected to that URL. If value is begins with a slash `/`, the user should be redirected to that path in their newly deployed app.",
44
- "type": "string",
45
- "allowEmpty": false,
46
- "example": "/welcome"
47
- },
48
- "scripts": {
49
- "description": "A key-value object specifying scripts or shell commands to execute at different stages in the build/release process.",
50
- "type": "object",
51
- "example": {"postdeploy": "bundle exec rake bootstrap"}
52
- },
53
- "env": {
54
- "description": "A key-value object for environment variables, or config vars in Heroku parlance. Keys are the names of the environment variables.\n\nValues can be strings or objects. If the value is a string, it will be used and the user will not be prompted to specify a different value. If the value is an object, it defines specific requirements for that variable:\n\ndescription - a human-friendly blurb about what the value is for and how to determine what it should be\nvalue - a default value to use\ngenerator - a string representing a function to call to generate the value, such as cookie secret",
55
- "type": "object",
56
- "example": {
57
- "BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
58
- "SECRET_TOKEN": {
59
- "description": "A secret key for verifying the integrity of signed cookies.",
60
- "generator": "secret"
61
- },
62
- "WEB_CONCURRENCY": {
63
- "description": "The number of processes to run.",
64
- "default": "5"
65
- }
66
- }
67
- },
68
- "addons": {
69
- "description": "An array of strings specifying Heroku addons to provision on the app before deploying. Each addon should be in the format `addon:plan`. If plan is omitted, that addon's default plan will be provisioned.",
70
- "type": "array",
71
- "example": [
72
- "openredis",
73
- "mongolab:shared-single-small"
74
- ]
75
- }
76
- }
77
- }
78
-
79
- // Assemble an example schema
80
- schema.example = {}
81
- Object.keys(schema.properties).map(function(key){
82
- schema.example[key] = schema.properties[key].example
83
- })
84
-
85
- // Assemble a template-ready stringified version of the schema
86
- schema.exampleJSON = JSON.stringify(schema.example, null, 2)
87
-
88
- // Coerce schema properties into a template-friendly format
89
- schema.propertiesArray = Object.keys(schema.properties).map(function(name) {
90
- var prop = schema.properties[name]
91
- prop.name = name
92
- prop.requiredOrOptional = prop.required ? "required" : "optional"
93
-
94
- var jsonDoc = {}
95
- jsonDoc[prop.name] = prop.example
96
- prop.exampleJSON = JSON.stringify(jsonDoc, null, 2)
97
- return prop
98
- })
99
-
100
- module.exports = schema
@@ -1,36 +0,0 @@
1
- <li class="app">
2
-
3
- <a class="logo activator">
4
- <img src="{{logo}}">
5
- </a>
6
-
7
- <div class="meta">
8
-
9
- <h2><a class="activator">{{name}}</a></h2>
10
-
11
- <div class="drawer">
12
-
13
- {{#description}}
14
- <p>{{description}}</p>
15
- {{/description}}
16
-
17
- {{#repository}}
18
- <a href="{{repository}}" class="repository">{{repository}}</a>
19
- {{/repository}}
20
-
21
- {{#website}}
22
- <a href="{{website}}" class="website">{{website}}</a>
23
- {{/website}}
24
-
25
- <form class="deploy">
26
- <input type="hidden" name="source" value="{{repository}}">
27
- <input type="submit" value="Deploy for {{prices.totalPrice}}">
28
- </form>
29
-
30
- <div class="output"></div>
31
-
32
- </div>
33
-
34
- </div>
35
-
36
- </li>
@@ -1,13 +0,0 @@
1
- {{#app}}
2
- <p>
3
- Your app is deploying to
4
- <a href="https://{{app.name}}.herokuapp.com">{{app.name}}.herokuapp.com</a>,
5
- and will be ready soon.
6
- </p>
7
- {{/app}}
8
-
9
- {{^app}}
10
- <p class="error">
11
- Build failed. {{message}}
12
- </p>
13
- {{/app}}
@@ -1,20 +0,0 @@
1
- `app.json` is a manifest format for describing web apps. It declares environment
2
- variables, addons, and other information required to run apps on Heroku. This document describes the schema in detail.
3
-
4
- ## Example app.json
5
-
6
- ```json
7
- {{{exampleJSON}}}
8
- ```
9
-
10
- ## The Schema
11
-
12
- {{#propertiesArray}}
13
- ### {{name}}
14
-
15
- {{description}} *{{requiredOrOptional}} {{type}}*
16
-
17
- ```json
18
- {{{exampleJSON}}}
19
- ```
20
- {{/propertiesArray}}
package/test/addons.js DELETED
@@ -1,172 +0,0 @@
1
- var assert = require("assert")
2
- var util = require("util")
3
- var addons = require("../lib/addons")
4
-
5
- describe('addons.getPlan()', function(){
6
-
7
- it('accepts an addon:plan string', function(done){
8
- addons.getPlan('mongohq:ssd_1g_elastic', function(err, plan) {
9
- assert.equal(plan.name, 'mongohq:ssd_1g_elastic')
10
- assert.equal(plan.description, 'MongoHQ 1 GB SSD')
11
- done()
12
- })
13
- })
14
-
15
- it('accepts an addon string with no plan', function(done){
16
- addons.getPlan('mongohq', function(err, plan) {
17
- assert.equal(plan.name, 'mongohq:sandbox')
18
- done()
19
- })
20
- })
21
-
22
- it('returns a pretty price', function(done){
23
- addons.getPlan('mongohq', function(err, plan) {
24
- assert.equal(plan.prettyPrice, 'Free')
25
- done()
26
- })
27
- })
28
-
29
- it('returns a logo URL when given an addon:plan slug', function(done){
30
- addons.getPlan('mongohq:sandbox', function(err, plan) {
31
- assert.equal(plan.logo, 'https://addons.heroku.com/addons/mongohq/icons/original.png')
32
- done()
33
- })
34
- })
35
-
36
- it('returns a logo URL given a plan-free slug', function(done){
37
- addons.getPlan('mongohq', function(err, plan) {
38
- assert.equal(plan.logo, 'https://addons.heroku.com/addons/mongohq/icons/original.png')
39
- done()
40
- })
41
- })
42
-
43
- })
44
-
45
- describe('addons.getPrices()', function(){
46
-
47
- it('accepts an array and returns a prcies object', function(done){
48
- addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
49
- assert(typeof(prices) === "object")
50
- assert(!util.isArray(prices))
51
- assert(util.isArray(prices.plans))
52
- done()
53
- })
54
- })
55
-
56
- it('returns an array of plans in the prices object', function(done){
57
- addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
58
- assert(util.isArray(prices.plans))
59
- done()
60
- })
61
- })
62
-
63
- it('handles addon:plan formatted slugs', function(done){
64
- addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
65
- assert.equal(prices.plans[0].name, 'mongohq:ssd_1g_elastic')
66
- assert.equal(prices.plans[0].price.cents, 1800)
67
- assert.equal(prices.plans[0].price.unit, 'month')
68
- done()
69
- })
70
- })
71
-
72
- it('finds the default plan, if unspecified', function(done){
73
- addons.getPrices(['mongohq'], function(err, prices) {
74
- assert.equal(prices.plans[0].name, 'mongohq:sandbox')
75
- assert.equal(prices.plans[0].price.cents, 0)
76
- assert.equal(prices.plans[0].price.unit, 'month')
77
- done()
78
- })
79
- })
80
-
81
- it('returns a totalPrice in the prices object', function(done){
82
- addons.getPrices(['mongohq:ssd_1g_elastic', 'memcachedcloud:100'], function(err, prices) {
83
- assert.equal(prices.plans[0].name, 'mongohq:ssd_1g_elastic')
84
- assert.equal(prices.plans[0].price.cents, 1800)
85
- assert.equal(prices.plans[1].price.cents, 1400)
86
- assert.equal(prices.totalPriceInCents, 3200)
87
- done()
88
- })
89
- })
90
-
91
- it('returns a human-friendly dollar amount total', function(done){
92
- addons.getPrices(['mongohq:ssd_1g_elastic', 'memcachedcloud:100'], function(err, prices) {
93
- assert.equal(prices.totalPriceInCents, 3200)
94
- assert.equal(prices.totalPrice, '$32/mo')
95
- done()
96
- })
97
- })
98
-
99
- it('returns free if total is zero', function(done){
100
- addons.getPrices(['heroku-postgresql'], function(err, prices) {
101
- assert.equal(prices.totalPriceInCents, 0)
102
- assert.equal(prices.totalPrice, 'Free')
103
- done()
104
- })
105
- })
106
-
107
- it('accepts an emtpy array', function(done){
108
- addons.getPrices([], function(err, prices) {
109
- assert(util.isArray(prices.plans))
110
- assert.equal(prices.totalPriceInCents, 0)
111
- assert.equal(prices.totalPrice, 'Free')
112
- done()
113
- })
114
- })
115
-
116
- it('propagates errors for nonexistent addons', function(done){
117
- addons.getPrices(['nonexistent-addon'], function(err, prices) {
118
- assert(err)
119
- done()
120
- })
121
- })
122
-
123
- it('propagates errors for nonexistent plans',function(done){
124
- addons.getPrices(['mongohq:bad-plan'], function(err, res) {
125
- assert(err)
126
- assert.equal(err.id, "not_found")
127
- done()
128
- })
129
- })
130
-
131
- it('handles a long list of addons',function(done){
132
- var slugs = [
133
- 'mongohq:sandbox',
134
- 'redistogo',
135
- 'goinstant',
136
- 'rollbar',
137
- 'usersnap',
138
- 'bonsai:staging'
139
- ]
140
-
141
- addons.getPrices(slugs, function(err, prices) {
142
- assert(!err)
143
- assert(prices.plans)
144
- assert(prices.totalPrice)
145
- assert.equal(typeof(prices.totalPriceInCents), 'number')
146
- done()
147
- })
148
- })
149
-
150
- it("returns a mocked response for a null slugs array", function(done) {
151
- addons.getPrices(null, function(err, prices) {
152
- assert(prices)
153
- assert.equal(prices.totalPrice, "Free")
154
- assert.equal(prices.totalPriceInCents, 0)
155
- assert(util.isArray(prices.plans))
156
- assert.equal(prices.plans.length, 0)
157
- done()
158
- })
159
- })
160
-
161
- it("returns a mocked response for an empty slugs array", function(done) {
162
- addons.getPrices([], function(err, prices) {
163
- assert(prices)
164
- assert.equal(prices.totalPrice, "Free")
165
- assert.equal(prices.totalPriceInCents, 0)
166
- assert(util.isArray(prices.plans))
167
- assert.equal(prices.plans.length, 0)
168
- done()
169
- })
170
- })
171
-
172
- })