Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit bfbb44c

Browse files
author
Adam Kliment
committed
Merge pull request #145 from apiaryio/kubula/source_files_at_results
Load blueprint-file from provided URL from http/https
2 parents 85463fd + 8fb977b commit bfbb44c

File tree

6 files changed

+157
-14
lines changed

6 files changed

+157
-14
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ If `beforeAll` and `afterAll` are called multiple times, the callbacks are execu
141141

142142
$ dredd --help
143143
Usage:
144-
dredd <path to blueprint> <api_endpoint> [OPTIONS]
144+
dredd <path or URL to blueprint> <api_endpoint> [OPTIONS]
145145

146146
Example:
147147
dredd ./apiary.md http://localhost:3000 --dry-run
@@ -193,8 +193,8 @@ If `beforeAll` and `afterAll` are called multiple times, the callbacks are execu
193193
[default: false]
194194
--silent, -q Silences commandline output.
195195
[default: false]
196-
--path, -p Additional blueprint paths. Can be used multiple times
197-
with glob pattern. [default: []]
196+
--path, -p Additional blueprint paths or URLs. Can be used multiple
197+
times with glob pattern for paths. [default: []]
198198
--help Show usage information.
199199

200200
--version Show version number.

bin/dredd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var Dredd = require('../lib/dredd');
77
var version = parsePackageJson(path.join(__dirname, '../package.json'));
88

99
var argv = optimist
10-
.usage("Usage: \n dredd <path to blueprint> <api_endpoint> [OPTIONS]" +
10+
.usage("Usage: \n dredd <path or URL to blueprint> <api_endpoint> [OPTIONS]" +
1111
"\n\nExample: \n " + "dredd ./apiary.md http://localhost:3000 --dry-run")
1212
.options(Dredd.options)
1313
.wrap(80)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"optimist": "~0.6.1",
3030
"protagonist": "~0.17.1",
3131
"proxyquire": "^1.3.1",
32+
"request": "^2.53.0",
3233
"setimmediate": "^1.0.2",
3334
"uri-template": "~1.0.0",
3435
"winston": "^0.9.0"

src/dredd.coffee

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ fs = require 'fs'
66
clone = require 'clone'
77
protagonist = require 'protagonist'
88
async = require 'async'
9+
request = require 'request'
10+
url = require 'url'
911

1012
logger = require './logger'
1113
options = require './options'
@@ -15,6 +17,8 @@ handleRuntimeProblems = require './handle-runtime-problems'
1517
blueprintAstToRuntime = require './blueprint-ast-to-runtime'
1618
configureReporters = require './configure-reporters'
1719

20+
CONNECTION_ERRORS = ['ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE']
21+
1822
class Dredd
1923
constructor: (configOrigin) ->
2024
# do not touch passed configuration, rather work on a clone of it
@@ -65,6 +69,9 @@ class Dredd
6569
# expand all globs
6670
expandGlobs = (cb) ->
6771
async.each config.options.path, (globToExpand, globCallback) ->
72+
if /^http(s)?:\/\//.test globToExpand
73+
config.files = config.files.concat globToExpand
74+
return globCallback()
6875
glob globToExpand, (err, match) ->
6976
globCallback err if err
7077
config.files = config.files.concat match
@@ -84,16 +91,43 @@ class Dredd
8491

8592
# load all files
8693
loadFiles = (cb) ->
87-
async.each config.files, (file, loadCallback) ->
88-
fs.readFile file, 'utf8', (loadingError, data) ->
89-
return loadCallback(loadingError) if loadingError
90-
config.data[file] = {raw: data, filename: file}
91-
loadCallback()
92-
93-
, (err) =>
94+
# 6 parallel connections is a standard limit when connecting to one hostname,
95+
# use the same limit of parallel connections for reading/downloading files
96+
async.eachLimit config.files, 6, (fileUrlOrPath, loadCallback) ->
97+
try
98+
fileUrl = url.parse fileUrlOrPath
99+
catch
100+
fileUrl = null
101+
102+
if fileUrl and fileUrl.protocol in ['http:', 'https:'] and fileUrl.host
103+
downloadFile fileUrlOrPath, loadCallback
104+
else
105+
readLocalFile fileUrlOrPath, loadCallback
106+
107+
, (err) ->
94108
return callback(err, stats) if err
95109
cb()
96110

111+
downloadFile = (fileUrl, downloadCallback) ->
112+
request.get
113+
url: fileUrl
114+
timeout: 5000
115+
json: false
116+
, (downloadError, res, body) ->
117+
if downloadError
118+
downloadCallback {message: "Error when loading file from URL '#{fileUrl}'. Is the provided URL correct?"}
119+
else if not body or res.statusCode < 200 or res.statusCode >= 300
120+
downloadCallback {message: "Unable to load file from URL '#{fileUrl}'. Server did not send any blueprint back and responded with status code #{res.statusCode}."}
121+
else
122+
config.data[fileUrl] = {raw: body, filename: fileUrl}
123+
downloadCallback()
124+
125+
readLocalFile = (filePath, readCallback) ->
126+
fs.readFile filePath, 'utf8', (readingError, data) ->
127+
return readCallback(readingError) if readingError
128+
config.data[filePath] = {raw: data, filename: filePath}
129+
readCallback()
130+
97131
# parse all file blueprints
98132
parseBlueprints = (cb) ->
99133
async.each Object.keys(config.data), (file, parseCallback) ->

src/options.coffee

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ options =
8181

8282
path:
8383
alias: "p"
84-
description: "Additional blueprint paths. Can be used multiple times with glob pattern."
84+
description: "Additional blueprint paths or URLs. Can be used multiple times with glob pattern for paths."
8585
default: []
8686

8787
help:

test/integration/cli-test.coffee

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ clone = require 'clone'
55
bodyParser = require 'body-parser'
66
fs = require 'fs'
77

8-
PORT = '3333'
8+
PORT = 3333
99
CMD_PREFIX = ''
1010

1111
stderr = ''
@@ -38,7 +38,7 @@ execCommand = (cmd, options = {}, callback) ->
3838

3939
describe "Command line interface", () ->
4040

41-
describe "When blueprint file not found", (done) ->
41+
describe "When blueprint file not found", ->
4242
before (done) ->
4343
cmd = "./bin/dredd ./test/fixtures/nonexistent_path.md http://localhost:#{PORT}"
4444

@@ -50,6 +50,114 @@ describe "Command line interface", () ->
5050
it 'should print error message to stderr', () ->
5151
assert.include stderr, 'not found'
5252

53+
54+
describe "When blueprint file should be loaded from 'http(s)://...' url", ->
55+
server = null
56+
loadedFromServer = null
57+
connectedToServer = null
58+
notFound = null
59+
fileFound = null
60+
61+
errorCmd = "./bin/dredd http://localhost:#{PORT+1}/connection-error.apib http://localhost:#{PORT+1}"
62+
wrongCmd = "./bin/dredd http://localhost:#{PORT}/not-found.apib http://localhost:#{PORT}"
63+
goodCmd = "./bin/dredd http://localhost:#{PORT}/file.apib http://localhost:#{PORT}"
64+
65+
afterEach ->
66+
connectedToServer = null
67+
68+
before (done) ->
69+
app = express()
70+
71+
app.use (req, res, next) ->
72+
connectedToServer = true
73+
next()
74+
75+
app.get '/', (req, res) ->
76+
res.sendStatus 404
77+
78+
app.get '/file.apib', (req, res) ->
79+
fileFound = true
80+
res.type('text')
81+
stream = fs.createReadStream './test/fixtures/single-get.apib'
82+
stream.pipe res
83+
84+
app.get '/machines', (req, res) ->
85+
res.setHeader 'Content-Type', 'application/json'
86+
machine =
87+
type: 'bulldozer'
88+
name: 'willy'
89+
response = [machine]
90+
res.status(200).send response
91+
92+
app.get '/not-found.apib', (req, res) ->
93+
notFound = true
94+
res.status(404).end()
95+
96+
server = app.listen PORT, ->
97+
done()
98+
99+
after (done) ->
100+
server.close ->
101+
app = null
102+
server = null
103+
done()
104+
105+
describe 'and I try to load a file from bad hostname at all', ->
106+
before (done) ->
107+
execCommand errorCmd, ->
108+
done()
109+
110+
after ->
111+
connectedToServer = null
112+
113+
it 'should not send a GET to the server', ->
114+
assert.isNull connectedToServer
115+
116+
it 'should exit with status 1', ->
117+
assert.equal exitStatus, 1
118+
119+
it 'should print error message to stderr', ->
120+
assert.include stderr, 'Error when loading file from URL'
121+
assert.include stderr, 'Is the provided URL correct?'
122+
assert.include stderr, 'connection-error.apib'
123+
124+
describe 'and I try to load a file that does not exist from an existing server', ->
125+
before (done) ->
126+
execCommand wrongCmd, ->
127+
done()
128+
129+
after ->
130+
connectedToServer = null
131+
132+
it 'should connect to the right server', ->
133+
assert.isTrue connectedToServer
134+
135+
it 'should send a GET to server at wrong URL', ->
136+
assert.isTrue notFound
137+
138+
it 'should exit with status 1', ->
139+
assert.equal exitStatus, 1
140+
141+
it 'should print error message to stderr', ->
142+
assert.include stderr, 'Unable to load file from URL'
143+
assert.include stderr, 'responded with status code 404'
144+
assert.include stderr, 'not-found.apib'
145+
146+
describe 'and I try to load a file that actually is there', ->
147+
before (done) ->
148+
execCommand goodCmd, ->
149+
done()
150+
151+
it 'should send a GET to the right server', ->
152+
assert.isTrue connectedToServer
153+
154+
it 'should send a GET to server at good URL', ->
155+
assert.isTrue fileFound
156+
157+
it 'should exit with status 0', ->
158+
assert.equal exitStatus, 0
159+
160+
53161
describe "Arguments with existing blueprint and responding server", () ->
54162
describe "when executing the command and the server is responding as specified in the blueprint", () ->
55163

0 commit comments

Comments
 (0)