├── .gitignore ├── .jshintrc ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── bin └── eyeserver ├── lib ├── eye.js └── eyeserver.js ├── package-lock.json ├── package.js ├── package.json └── test ├── eye-test.js ├── eyeserver-test.js └── spawnasserter.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, // Allow use of node.js globals. 3 | "loopfunc": true, // Allow functions to be defined within loops. 4 | "proto": true, // Allow use of `__proto__`. 5 | "trailing": true, // Do not leave trailing whitespace. 6 | "white": true, // Enable scrict whitespace checking. 7 | "indent": 2 // The code uses an indentation width of 2 spaces. 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eyereasoner/eye:latest 2 | LABEL maintainer="https://github.com/eyereasoner/" 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | npm \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | RUN npm -g install eyeserver 9 | 10 | EXPOSE 8000 11 | CMD ["8000"] 12 | ENTRYPOINT ["eyeserver"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | Thet MIT License (MIT) 3 | Copyright ©2011–2013 Ruben Verborgh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @./node_modules/.bin/vows 3 | 4 | jshint: 5 | @./node_modules/jshint/bin/hint lib bin 6 | 7 | .PHONY: test jshint 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EyeServer is a server for the EYE reasoner. 2 | 3 | [**Reasoning**](http://n3.restdesc.org/) is the powerful mechanism to draw conclusions from facts. 4 | The Semantic Web contains vast amounts of data, 5 | which makes it an interesting source to use with one of several available reasoners. 6 | 7 | [**Reasoning in your browser**](https://github.com/RubenVerborgh/EyeClient) is possible with this server, which exposes the [EYE](http://eulersharp.sourceforge.net/) N3 reasoner to the Web. 8 | 9 | [**Bringing reasoning to the Web**](http://reasoning.restdesc.org/) is the initiative with several open source projects (such as this one) that make reasoning accessible. 10 | 11 | ## EyeServer is a counterpart to EyeClient. 12 | [EyeClient](https://github.com/RubenVerborgh/EyeClient) is a browser widget that communicates with an EYE reasoner server to deliver reasoning results. 13 | 14 | [![The widget (on the client) and the reasoner (on the server) interact.](http://reasoning.restdesc.org/images/reasoner-client-server.png)](http://reasoning.restdesc.org/) 15 | 16 | ## Run your own EyeServer or use a public one. 17 | Follow the instructions below to set up your own reasoner server, 18 | or use our public reasoner server at `http://eye.restdesc.org/`. 19 | 20 | ### Installing 21 | EyeServer is an [npm](http://npmjs.org/) package for [node.js](http://nodejs.org/). 22 | 23 | First of all, you need to **install the EYE reasoner** ([Windows](http://eulersharp.sourceforge.net/README.Windows) – [OS X](http://eulersharp.sourceforge.net/README.MacOSX) – [Linux](http://eulersharp.sourceforge.net/README.Linux)). 24 | 25 | Then, **install the server package** as follows: 26 | 27 | ``` bash 28 | $ [sudo] npm -g install eyeserver 29 | ``` 30 | 31 | ### Running 32 | 33 | ``` bash 34 | $ eyeserver 8000 35 | ``` 36 | 37 | ### Using 38 | 39 | ``` bash 40 | $ curl "http://localhost:8000/?data=https://n3.restdesc.org/n3/friends.n3&data=https://n3.restdesc.org/n3/knows-rule.n3&query=https://n3.restdesc.org/n3/query-all.n3" 41 | ``` 42 | 43 | ## Learn more. 44 | 45 | The [Bringing reasoning to the Web](http://reasoning.restdesc.org/) page explains the origins of this project and provides pointers to related resources. 46 | 47 | This code is written by Ruben Verborgh and serves as an HTTP interface to the [EYE](http://eulersharp.sourceforge.net/) reasoner by Jos De Roo. 48 | -------------------------------------------------------------------------------- /bin/eyeserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Increase the number of available sockets 4 | require("http").globalAgent.maxSockets = 64; 5 | 6 | var EyeServer = require('../lib/eyeserver.js'); 7 | 8 | var eyeServer = new EyeServer(); 9 | eyeServer.check(function (err) { 10 | if(err) { 11 | console.error('ERROR: Could not start EyeServer\n' + 12 | 'Possibly the eye command could not be found, ' + 13 | 'or /tmp has no write permission.\n\n' + 14 | 'Error detail:\n', err); 15 | process.exit(1); 16 | } 17 | else { 18 | var port = process.argv[2] ? parseInt(process.argv[2], 10) : 8000; 19 | eyeServer.listen(port); 20 | console.log('EYE server running on http://localhost:' + port); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /lib/eye.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn, 2 | EventEmitter = require('events').EventEmitter, 3 | ResourceCache = require('resourcecache'); 4 | 5 | var commentRegex = /^#.*$\n/mg, 6 | localIdentifierRegex = /<\/tmp\/([^#]+)#([^>]+>)/g, 7 | prefixDeclarationRegex = /^@prefix|PREFIX ([\w\-]*:) <([^>]+)>\.?\n/g, 8 | eyeSignatureRegex = /^(Id: euler\.yap|EYE)/m, 9 | errorRegex = /^\*\* ERROR \*\*\s*(.*)$/m; 10 | 11 | var flags = ['nope', 'noBranch', 'noDistinct', 'noQvars', 12 | 'noQnames', 'quiet', 'quickFalse', 'quickPossible', 13 | 'quickAnswer', 'think', 'ances', 'ignoreSyntaxError', 14 | 'pcl', 'strings', 'debug', 'profile', 'version', 'help', 15 | 'pass', 'passAll', 'traditional']; 16 | 17 | // An Eye object provides reasoning methods. 18 | function Eye(options) { 19 | options = options || {}; 20 | 21 | // dummy constructor to enable Eye construction without new 22 | function F() {} 23 | F.prototype = Eye.prototype; 24 | 25 | // create and return new Eye object 26 | var eye = new F(); 27 | eye.spawn = options.spawn; 28 | eye.eyePath = options.eyePath || /^win/.test(process.platform) ? 'eye.cmd' : 'eye'; 29 | eye.resourceCache = options.resourceCache || new ResourceCache(); 30 | return eye; 31 | } 32 | 33 | var eyePrototype = Eye.prototype = { 34 | constructor: Eye, 35 | 36 | defaults: { 37 | nope: true, 38 | data: [] 39 | }, 40 | 41 | pass: function (data, callback) { 42 | return this.execute({ data: data, pass: true }, callback); 43 | }, 44 | 45 | execute: function (options, callback) { 46 | var self = this; 47 | 48 | // set correct argument values (options is optional) 49 | if (typeof(options) === 'function') { 50 | callback = options; 51 | options = {}; 52 | } 53 | 54 | // add default options if applicable 55 | options = options || {}; 56 | for (var prop in this.defaults) { 57 | if (this.defaults.hasOwnProperty(prop) && typeof(options[prop]) === 'undefined') { 58 | options[prop] = this.defaults[prop]; 59 | } 60 | } 61 | 62 | // do a pass if no query specified, and pass not explicitely disabled 63 | if (!options.query && typeof(options.pass) === 'undefined') 64 | options.pass = true; 65 | 66 | // set EYE commandline arguments according to options 67 | var eye, 68 | args = [], 69 | resources = [], 70 | resourcesPending = 0, 71 | localResources = 0; 72 | flags.forEach(function (name) { 73 | if (options[name]) { 74 | args.push('--' + name.replace(/([A-Z])/g, '-$1').toLowerCase()); 75 | } 76 | }); 77 | 78 | // add data URIs 79 | if (typeof(options.data) === "string") 80 | options.data = [options.data]; 81 | options.data.forEach(addDataItem); 82 | 83 | // add query URI 84 | if (typeof(options.query) === "string") 85 | addDataItem(options.query, '--query'); 86 | else if (options.query instanceof Array) 87 | addDataItem(options.query[0], '--query'); 88 | 89 | function addDataItem(dataItem, modifier) { 90 | // does it contain a protocol name of some sort? 91 | if (dataItem.match(/^[a-zA-Z0-9]+:/)) { 92 | // is it HTTP(S), but not on a reserved domain? 93 | if (dataItem.match(/^https?:\/\/(?!localhost|127\.0\.0\.1|[0:]*:1)/)) { 94 | // cache the resource and add it 95 | self.resourceCache.cacheFromUrl(dataItem, 'text/n3,text/turtle,*/*;q=.1', addResourceCallback(dataItem, modifier)); 96 | } 97 | } 98 | // the data resource is assumed to be N3 now, 99 | // so a new data resource has to be created from the N3 string 100 | else { 101 | // cache the N3 string in a file and add it 102 | self.resourceCache.cacheFromString(dataItem, addResourceCallback("tmp/" + (++localResources), modifier)); 103 | } 104 | } 105 | 106 | // returns a callback that will pass the resource to EYE 107 | function addResourceCallback(uri, modifier) { 108 | // pass possible data modifier (such as '--query') 109 | if (typeof(modifier) === 'string') 110 | args.push(modifier); 111 | // pass the URI of the cached item 112 | args.push(uri); 113 | 114 | // since the resource cache file will be created asynchronously, 115 | // we need to keep track of the number of pending resources. 116 | resourcesPending++; 117 | // return a callback for resourceCache 118 | return function (err, cacheFile) { 119 | if (err) { 120 | if (callback) { 121 | callback(err, null); 122 | callback = null; 123 | } 124 | return; 125 | } 126 | 127 | // tell in what file the resource with the URI has been cached 128 | args.push("--wcache"); 129 | args.push(uri); 130 | args.push(cacheFile); 131 | 132 | // keep track of gathered resources 133 | resources.push(cacheFile); 134 | resourcesPending--; 135 | 136 | // start EYE if no more resources are pending 137 | if (!resourcesPending) 138 | startEye(); 139 | }; 140 | } 141 | 142 | // start EYE if no more resources are pending 143 | if (!resourcesPending) 144 | startEye(); 145 | 146 | function startEye() { 147 | // make sure not to start EYE twice 148 | if (eye) 149 | return; 150 | 151 | // start EYE 152 | eye = (self.spawn || spawn)(self.eyePath, args); 153 | eye.on('error', callback); 154 | 155 | // capture stdout 156 | var output = ""; 157 | eye.stdout.on('data', function (data) { 158 | output += data; 159 | }); 160 | eye.stdout.once('end', function () { 161 | eye.stdout.finished = true; 162 | eye.stdout.removeAllListeners(); 163 | eyeFinished(); 164 | }); 165 | 166 | // capture stderr 167 | var error = ""; 168 | eye.stderr.on('data', function (data) { 169 | error += data; 170 | }); 171 | eye.stderr.once('end', function () { 172 | eye.stderr.finished = true; 173 | eye.stderr.removeAllListeners(); 174 | eyeFinished(); 175 | }); 176 | 177 | // after both streams are complete, report output or error 178 | function eyeFinished() { 179 | if (!(eye.stdout.finished && eye.stderr.finished)) 180 | return; 181 | 182 | resources.forEach(function (resource) { 183 | self.resourceCache.release(resource, function () {}); 184 | }); 185 | 186 | if (callback) { 187 | // has EYE not been executed? 188 | if (!error.match(eyeSignatureRegex)) 189 | callback(error, null); 190 | // EYE has been executed 191 | else { 192 | var errorMatch = error.match(errorRegex); 193 | // did EYE report errors? 194 | if (errorMatch) 195 | callback(errorMatch[1], null); 196 | // EYE reported no errors 197 | else 198 | callback(null, self.clean(output)); 199 | } 200 | callback = null; 201 | } 202 | eye = null; 203 | } 204 | } 205 | 206 | function stopEye() { 207 | if (eye) { 208 | eye.removeAllListeners('exit'); 209 | eye.stdout.removeAllListeners(); 210 | eye.stderr.removeAllListeners(); 211 | eye.kill(); 212 | eye = null; 213 | } 214 | } 215 | 216 | // return status object 217 | var status = new EventEmitter(); 218 | status.cancel = stopEye; 219 | return status; 220 | }, 221 | 222 | clean: function (n3) { 223 | // remove comments 224 | n3 = n3.replace(commentRegex, ''); 225 | 226 | // remove prefix declarations from the document, storing them in an object 227 | var prefixes = {}; 228 | n3 = n3.replace(prefixDeclarationRegex, function (match, prefix, namespace) { 229 | prefixes[prefix] = namespace.replace(/^file:\/\/.*?([^\/]+)$/, '$1'); 230 | return ''; 231 | }); 232 | 233 | // remove unnecessary whitespace from the document 234 | n3 = n3.trim(); 235 | 236 | // find the used prefixes 237 | var prefixLines = []; 238 | for (var prefix in prefixes) { 239 | var namespace = prefixes[prefix]; 240 | 241 | // EYE does not use prefixes of namespaces ending in a slash (instead of a hash), 242 | // so we apply them manually 243 | if (namespace.match(/\/$/)) 244 | // warning: this could wreck havoc inside string literals 245 | n3 = n3.replace(new RegExp('<' + escapeForRegExp(namespace) + '(\\w+)>', 'gm'), prefix + '$1'); 246 | 247 | // add the prefix if it's used 248 | // (we conservatively employ a wide definition of "used") 249 | if (n3.match(prefix)) 250 | prefixLines.push("PREFIX ", prefix, " <", namespace, ">\n"); 251 | } 252 | 253 | // join the used prefixes and the rest of the N3 254 | return !prefixLines.length ? n3 : (prefixLines.join('') + '\n' + n3); 255 | } 256 | }; 257 | 258 | Object.defineProperty(Eye, 'flags', { value: flags }); 259 | Object.defineProperty(Eye, 'flagNames', { value: flags }); 260 | 261 | // Expose each of the Eye instance functions also as static functions. 262 | // They behave as instance functions on a new Eye object. 263 | for (var propertyName in eyePrototype) { 264 | if (eyePrototype.hasOwnProperty(propertyName) && typeof(eyePrototype[propertyName]) === 'function') { 265 | (function (propertyName) { 266 | Eye[propertyName] = function () { 267 | return eyePrototype[propertyName].apply(new Eye(), arguments); 268 | }; 269 | })(propertyName); 270 | } 271 | } 272 | 273 | function escapeForRegExp(text) { 274 | return text.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); 275 | } 276 | 277 | module.exports = Eye; 278 | -------------------------------------------------------------------------------- /lib/eyeserver.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | Eye = require('./eye'); 3 | 4 | var flags = {}; 5 | Eye.flags.forEach(function (option) { 6 | flags[option.toLowerCase()] = option; 7 | }); 8 | 9 | function EyeServer(options) { 10 | // dummy constructor to enable EyeServer construction without new 11 | function F() {} 12 | F.prototype = EyeServer.prototype; 13 | 14 | // create new EyeServer, inheriting from express.HTTPServer 15 | var eyeServer = new F(); 16 | express.HTTPServer.call(eyeServer, []); 17 | 18 | // apply settings and defaults 19 | eyeServer.settings = options || {}; 20 | eyeServer.settings.eye = eyeServer.settings.eye || new Eye(); 21 | 22 | // initialize server 23 | eyeServer.use(express.bodyParser()); 24 | eyeServer.get(/^\/$/, proxy(eyeServer, eyeServer.handleEyeRequest)); 25 | eyeServer.post(/^\/$/, proxy(eyeServer, eyeServer.handleEyeRequest)); 26 | eyeServer.options(/^\/$/, proxy(eyeServer, eyeServer.handleEyeOptionsRequest)); 27 | 28 | return eyeServer; 29 | } 30 | 31 | EyeServer.prototype = { 32 | constructor: EyeServer, 33 | 34 | // inherit from express.HTTPServer 35 | __proto__: express.HTTPServer.prototype, 36 | 37 | handleEyeRequest: function (req, res, next) { 38 | var self = this, 39 | reqParams = req.query, 40 | body = req.body || {}, 41 | data = reqParams.data || [], 42 | query = reqParams.query || body.query, 43 | jsonpCallback = reqParams.callback, 44 | eyeParams = {}; 45 | 46 | // make sure data is an array 47 | if (typeof(data) === 'string') 48 | data = data.split(','); 49 | 50 | // add body data 51 | if (typeof(body.data) === 'string') 52 | data.push(body.data); 53 | else if (body.data instanceof Array) 54 | data.push.apply(data, body.data); 55 | 56 | // collect data and data URIs 57 | eyeParams.data = []; 58 | // inspect all data parameters in request parameters 59 | data.forEach(function (item) { 60 | if (!item.match(/^https?:\/\//)) 61 | // item is N3 data – push it 62 | eyeParams.data.push(item); 63 | else 64 | // item is list of URIs – push each of them 65 | eyeParams.data.push.apply(eyeParams.data, item.split(',')); 66 | }); 67 | 68 | // do a reasoner pass by default 69 | eyeParams.pass = true; 70 | 71 | // add query if present 72 | if (query) { 73 | eyeParams.query = query; 74 | delete eyeParams.pass; 75 | } 76 | 77 | // add flags 78 | for (var param in reqParams) { 79 | var flag = flags[param.replace(/-/g, '').toLowerCase()]; 80 | if (flag) 81 | eyeParams[flag] = !reqParams[param].match(/^0|false$/i); 82 | } 83 | 84 | // add debug information if requested 85 | if (this.settings.debug) 86 | eyeParams.originalUrl = req.originalUrl; 87 | 88 | // execute the reasoner and return result or error 89 | var eyeStatus = this.settings.eye.execute(eyeParams, function (error, result) { 90 | if (!jsonpCallback) { 91 | self.setDefaultHeaders(req, res); 92 | if (!error) { 93 | res.header('Content-Type', 'text/n3'); 94 | res.send(result + '\n'); 95 | } 96 | else { 97 | res.header('Content-Type', 'text/plain'); 98 | res.send(error + '\n', 400); 99 | } 100 | } 101 | else { 102 | res.header('Content-Type', 'application/javascript'); 103 | if (jsonpCallback.match(/^[\w\d\-_]+$/i)) 104 | res.send(jsonpCallback + '(' + JSON.stringify(error || result) + ')'); 105 | else 106 | res.send('alert("Illegal callback name.")', 400); 107 | } 108 | }); 109 | 110 | // cancel reasoning process if request is closed prematurely 111 | req.once('close', proxy(eyeStatus, eyeStatus.cancel)); 112 | }, 113 | 114 | handleEyeOptionsRequest: function (req, res, next) { 115 | this.setDefaultHeaders(req, res); 116 | res.header('Content-Type', 'text/plain'); 117 | res.send(''); 118 | }, 119 | 120 | setDefaultHeaders: function (req, res) { 121 | res.header('X-Powered-By', 'EYE Server'); 122 | res.header('Access-Control-Allow-Origin', '*'); 123 | }, 124 | 125 | check: function (callback) { 126 | this.settings.eye.execute({ data: 'prefix : \n :a :b :c.' }, callback); 127 | } 128 | }; 129 | 130 | function proxy(object, method) { 131 | return function () { method.apply(object, arguments); }; 132 | } 133 | 134 | module.exports = EyeServer; 135 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eyeserver", 3 | "version": "1.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "argsparser": { 8 | "version": "0.0.7", 9 | "resolved": "https://registry.npmjs.org/argsparser/-/argsparser-0.0.7.tgz", 10 | "integrity": "sha1-QcheDD3nV7NQ8S5u0OSQsegtvgY=" 11 | }, 12 | "balanced-match": { 13 | "version": "1.0.0", 14 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 15 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 16 | }, 17 | "brace-expansion": { 18 | "version": "1.1.11", 19 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 20 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 21 | "requires": { 22 | "balanced-match": "^1.0.0", 23 | "concat-map": "0.0.1" 24 | } 25 | }, 26 | "concat-map": { 27 | "version": "0.0.1", 28 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 29 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 30 | }, 31 | "connect": { 32 | "version": "1.9.2", 33 | "resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz", 34 | "integrity": "sha1-QogKIulDiuWait105Df1iujlKAc=", 35 | "requires": { 36 | "formidable": "1.0.x", 37 | "mime": ">= 0.0.1", 38 | "qs": ">= 0.4.0" 39 | } 40 | }, 41 | "diff": { 42 | "version": "1.0.8", 43 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", 44 | "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", 45 | "dev": true 46 | }, 47 | "express": { 48 | "version": "2.5.11", 49 | "resolved": "http://registry.npmjs.org/express/-/express-2.5.11.tgz", 50 | "integrity": "sha1-TOjqHzY15p5J8Ou0l7aksKUc5vA=", 51 | "requires": { 52 | "connect": "1.x", 53 | "mime": "1.2.4", 54 | "mkdirp": "0.3.0", 55 | "qs": "0.4.x" 56 | } 57 | }, 58 | "eyes": { 59 | "version": "0.1.8", 60 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 61 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", 62 | "dev": true 63 | }, 64 | "formidable": { 65 | "version": "1.0.16", 66 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz", 67 | "integrity": "sha1-SRbP38TL7QILJXpqlQWpqzjCzQ4=" 68 | }, 69 | "fs.realpath": { 70 | "version": "1.0.0", 71 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 72 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 73 | }, 74 | "glob": { 75 | "version": "7.1.3", 76 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 77 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 78 | "requires": { 79 | "fs.realpath": "^1.0.0", 80 | "inflight": "^1.0.4", 81 | "inherits": "2", 82 | "minimatch": "^3.0.4", 83 | "once": "^1.3.0", 84 | "path-is-absolute": "^1.0.0" 85 | } 86 | }, 87 | "inflight": { 88 | "version": "1.0.6", 89 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 90 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 91 | "requires": { 92 | "once": "^1.3.0", 93 | "wrappy": "1" 94 | } 95 | }, 96 | "inherits": { 97 | "version": "2.0.3", 98 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 99 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 100 | }, 101 | "jshint": { 102 | "version": "0.3.1", 103 | "resolved": "https://registry.npmjs.org/jshint/-/jshint-0.3.1.tgz", 104 | "integrity": "sha1-Jg3x6qpkYr7PFpPaVegx+oery2k=", 105 | "requires": { 106 | "argsparser": ">=0.0.3", 107 | "glob": ">=2.0.7" 108 | } 109 | }, 110 | "mime": { 111 | "version": "1.2.4", 112 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.4.tgz", 113 | "integrity": "sha1-EbX9rynCUJJVF2uArVIClPXekrc=" 114 | }, 115 | "minimatch": { 116 | "version": "3.0.4", 117 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 118 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 119 | "requires": { 120 | "brace-expansion": "^1.1.7" 121 | } 122 | }, 123 | "mkdirp": { 124 | "version": "0.3.0", 125 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", 126 | "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" 127 | }, 128 | "once": { 129 | "version": "1.4.0", 130 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 131 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 132 | "requires": { 133 | "wrappy": "1" 134 | } 135 | }, 136 | "path-is-absolute": { 137 | "version": "1.0.1", 138 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 139 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 140 | }, 141 | "qs": { 142 | "version": "0.4.2", 143 | "resolved": "https://registry.npmjs.org/qs/-/qs-0.4.2.tgz", 144 | "integrity": "sha1-PKxMhh43GoycR3CsI82o3mObjl8=" 145 | }, 146 | "request": { 147 | "version": "2.2.9", 148 | "resolved": "http://registry.npmjs.org/request/-/request-2.2.9.tgz", 149 | "integrity": "sha1-77+K+/5/HiANSDuZdStsQrQEoPE=", 150 | "dev": true 151 | }, 152 | "resourcecache": { 153 | "version": "0.1.2", 154 | "resolved": "https://registry.npmjs.org/resourcecache/-/resourcecache-0.1.2.tgz", 155 | "integrity": "sha1-wKZEM0ltctolAKUsv40IRtNkmDg=" 156 | }, 157 | "should": { 158 | "version": "0.3.2", 159 | "resolved": "https://registry.npmjs.org/should/-/should-0.3.2.tgz", 160 | "integrity": "sha1-b/+b3Ivr9CKtj1S273CLDbpRFq8=", 161 | "dev": true 162 | }, 163 | "vows": { 164 | "version": "0.6.4", 165 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.6.4.tgz", 166 | "integrity": "sha1-/w7V1lTQTTl87jLc5DhOqIy08ZM=", 167 | "dev": true, 168 | "requires": { 169 | "diff": "~1.0.3", 170 | "eyes": ">=0.1.6" 171 | } 172 | }, 173 | "wrappy": { 174 | "version": "1.0.2", 175 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 176 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | exports.Eye = require('./lib/eye'); 2 | exports.EyeServer = require('./lib/eyeserver'); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eyeserver", 3 | "version": "1.1.2", 4 | "description": "EYE reasoner server", 5 | "author": "Ruben Verborgh ", 6 | "keywords": [ 7 | "eye", 8 | "reasoner", 9 | "n3" 10 | ], 11 | "license": "MIT", 12 | "main": "./package.js", 13 | "bin": "./bin/eyeserver", 14 | "engines": { 15 | "node": ">=0.6.0" 16 | }, 17 | "dependencies": { 18 | "express": "~2.5.2", 19 | "resourcecache": "~0.1.0", 20 | "jshint": "~0.3.0" 21 | }, 22 | "devDependencies": { 23 | "vows": "~0.6.0", 24 | "should": "~0.3.2", 25 | "request": "~2.2.9" 26 | }, 27 | "scripts": { 28 | "test": "make test" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "http://github.com/RubenVerborgh/EyeServer.git" 33 | }, 34 | "bugs": { 35 | "url": "http://github.com/RubenVerborgh/EyeServer/issues" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/eye-test.js: -------------------------------------------------------------------------------- 1 | var eye = require('../lib/eye.js'); 2 | var vows = require('vows'), 3 | should = require('should'), 4 | fs = require('fs'), 5 | EventEmitter = require('events').EventEmitter, 6 | SpawnAsserter = require('./spawnasserter'); 7 | 8 | vows.describe('Eye').addBatch({ 9 | 'The eye module': { 10 | topic: function () { return eye; }, 11 | 12 | 'should be a function': function (eye) { 13 | eye.should.be.a('function'); 14 | }, 15 | 16 | 'should make Eye objects': function (eye) { 17 | eye().constructor.should.eql(eye); 18 | eye().should.be.an.instanceof(eye); 19 | }, 20 | 21 | 'should be an Eye constructor': function (eye) { 22 | new eye().constructor.should.eql(eye); 23 | new eye().should.be.an.instanceof(eye); 24 | }, 25 | 26 | 'should have a pass function': function (eye) { 27 | eye.pass.should.be.a('function'); 28 | }, 29 | 30 | 'should have an execute function': function (eye) { 31 | eye.execute.should.be.a('function'); 32 | }, 33 | 34 | 'should expose flag names': function (eye) { 35 | eye.should.have.property('flagNames'); 36 | eye.flagNames.should.eql(['nope', 'noBranch', 'noDistinct', 'noQvars', 37 | 'noQnames', 'quiet', 'quickFalse', 'quickPossible', 38 | 'quickAnswer', 'think', 'ances', 'ignoreSyntaxError', 39 | 'pcl', 'strings', 'debug', 'profile', 'version', 'help', 40 | 'pass', 'passAll']); 41 | } 42 | }, 43 | 'An Eye instance': { 44 | 'when executed without arguments': 45 | shouldExecuteEyeWith(null, 46 | "with 'nope' and 'pass'", 47 | ['--nope', '--pass']), 48 | 49 | 'when executed with nope to false': 50 | shouldExecuteEyeWith({ nope: false }, 51 | "without 'nope'", 52 | ['--pass']), 53 | 54 | 'when executed with pass to false': 55 | shouldExecuteEyeWith({ pass: false }, 56 | "without 'pass'", 57 | ['--nope']), 58 | 59 | 'when executed with boolean options': 60 | shouldExecuteEyeWith({ 'nope': true, 'noBranch': true, 'noDistinct': true, 61 | 'noQvars': true, 'noQnames': true, 'quiet': true, 62 | 'quickFalse': true, 'quickPossible': true, 'quickAnswer': true, 63 | 'think': true, 'ances': true, 'ignoreSyntaxError': true, 'pcl': true, 64 | 'strings': true, 'debug': true, 'profile': true, 'version': true, 65 | 'help': true, 'pass': true, 'passAll': true }, 66 | "should pass the options", 67 | ['--nope', '--no-branch', '--no-distinct', '--no-qvars', '--no-qnames', 68 | '--quiet', '--quick-false', '--quick-possible', '--quick-answer', 69 | '--think', '--ances', '--ignore-syntax-error', '--pcl', '--strings', 70 | '--debug', '--profile', '--version', '--help', '--pass', '--pass-all']), 71 | 72 | 'when executed with one data URI': 73 | shouldExecuteEyeWith({ data: 'http://ex.org/1' }, 74 | "with one data URI", 75 | ['--nope', '--pass', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri']), 76 | 77 | 'when executed with multiple data URIs': 78 | shouldExecuteEyeWith({ data: ['http://ex.org/1', 'http://ex.org/2'] }, 79 | "with multiple data URIs", 80 | ['--nope', '--pass', 'http://ex.org/1', 'http://ex.org/2', 81 | '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri', 82 | '--wcache', 'http://ex.org/2', 'http://ex.org/2_cachedUri']), 83 | 84 | 'when executed with localhost URIs': 85 | shouldExecuteEyeWith({ data: ['http://ex.org/1', 'http://localhost/2', 'https://localhost/3'] }, 86 | "without the localhost URIs", 87 | ['--nope', '--pass', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri']), 88 | 89 | 'when executed with 127.0.0.1 URIs': 90 | shouldExecuteEyeWith({ data: ['http://ex.org/1', 'http://127.0.0.1/2', 'https://127.0.0.1/3'] }, 91 | "without the 127.0.0.1 URIs", 92 | ['--nope', '--pass', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri']), 93 | 94 | 'when executed with ::1 URIs': 95 | shouldExecuteEyeWith({ data: ['http://ex.org/1', 'http://::1/2', 'https://::1/3'] }, 96 | "without the ::1 URIs", 97 | ['--nope', '--pass', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri']), 98 | 99 | 'when executed with file URIs': 100 | shouldExecuteEyeWith({ data: ['http://ex.org/1', 'file://example/'] }, 101 | "without the file URIs", 102 | ['--nope', '--pass', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri']), 103 | 104 | 'when executed with a query URI': 105 | shouldExecuteEyeWith({ query: 'http://ex.org/1' }, 106 | "with the query URI and without 'pass'", 107 | ['--nope', '--query', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri' ]), 108 | 109 | 'when executed with multiple query URIs': 110 | shouldExecuteEyeWith({ query: ['http://ex.org/1', 'http://ex.org/2'] }, 111 | "with the first query URI and without 'pass'", 112 | ['--nope', '--query', 'http://ex.org/1', '--wcache', 'http://ex.org/1', 'http://ex.org/1_cachedUri' ]), 113 | 114 | 'when executed with literal data': 115 | shouldExecuteEyeWith({ data: ':a :b :c.' }, 116 | 'with a temporary file', 117 | ['--nope', '--pass', 'tmp/1', '--wcache', 'tmp/1', ':a :b :c._cachedStr']), 118 | 119 | 'when executed with data that results in unused prefixes': 120 | shouldExecuteEyeWith({}, 121 | 'and remove unused prefixes', 122 | [ '--nope', '--pass' ], 123 | '@prefix ex-1: .\n' + 124 | '@prefix ex-2: .\n' + 125 | '@prefix ex-3: .\n' + 126 | 'ex-2:a ex-2:b ex-2:c.', 127 | '@prefix ex-2: .\n\n' + 128 | 'ex-2:a ex-2:b ex-2:c.'), 129 | 130 | 'when executed with data that results in prefixes for unhashed namespaces': 131 | shouldExecuteEyeWith({}, 132 | 'and use the prefixes for unhashed namespaces as well', 133 | [ '--nope', '--pass' ], 134 | '@prefix ex-1: .\n' + 135 | '@prefix ex-2: .\n' + 136 | ' .', 137 | '@prefix ex-1: .\n' + 138 | '@prefix ex-2: .\n\n' + 139 | 'ex-1:a ex-2:b ex-1:c.'), 140 | 'when execution results in an error': 141 | shouldExecuteEyeWith({}, 142 | "should return the error", 143 | ['--nope', '--pass'], 144 | null, null, 145 | "** ERROR ** Message", 146 | "Message"), 147 | 148 | 'when executed, returns an object that': (function () { 149 | var spawner = new SpawnAsserter(), 150 | emitter; 151 | return { 152 | topic: function () { 153 | emitter = new eye({ spawn: spawner.spawn }).execute(); 154 | return { result: emitter, spawner: spawner }; 155 | }, 156 | 'should be an EventEmitter': function (param) { 157 | should.exist(param.result); 158 | param.result.should.be.an.instanceof(EventEmitter); 159 | }, 160 | 'should terminate the EYE process when canceled': function (param) { 161 | should.not.exist(param.spawner.killed); 162 | param.result.cancel(); 163 | should.exist(param.spawner.killed); 164 | param.spawner.stdout.listeners('data').should.be.empty; 165 | param.spawner.stderr.listeners('data').should.be.empty; 166 | param.spawner.stdout.listeners('end').should.be.empty; 167 | param.spawner.stderr.listeners('end').should.be.empty; 168 | }, 169 | }; 170 | })(), 171 | 172 | 'when executed with multiple failing resources': { 173 | topic: function () { 174 | var self = this, 175 | remaining = 4, 176 | eyeResult = '', 177 | resourceCache = { 178 | cacheFromString: function (s, callback) { callback('ERR ' + s); cached(); }, 179 | cacheFromUrl : function (s, t, callback) { callback('ERR ' + s); cached(); }, 180 | }; 181 | 182 | function cached() { 183 | if (!(--remaining)) 184 | self.callback(eyeResult); 185 | } 186 | 187 | new eye({ resourceCache: resourceCache}) 188 | .execute({ data: ['a', 'b', 'http://a', 'http://b'] }, function (value) { 189 | eyeResult += value; 190 | }); 191 | }, 192 | 193 | 'only the first failing resource should fire the callback': function (err, value) { 194 | err.should.eql('ERR a'); 195 | should.not.exist(value); 196 | } 197 | }, 198 | } 199 | }).export(module); 200 | 201 | function executeEyeWith(options, errorText, outputText) { 202 | var eyeSignature = "Id: euler.yap\n"; 203 | 204 | return function () { 205 | var spawner = new SpawnAsserter(), 206 | cached = {}, 207 | resourceCache = { 208 | cacheFromString: function (s, callback) { 209 | var fileName = s + '_cachedStr'; 210 | cached[fileName] = true; 211 | process.nextTick(function () { callback(null, fileName); }); 212 | }, 213 | cacheFromUrl: function (u, contentType, callback) { 214 | var fileName = u + '_cachedUri'; 215 | cached[fileName] = true; 216 | process.nextTick(function () { callback(null, fileName); }); 217 | contentType.should.eql('text/n3,text/turtle,*/*;q=.1'); 218 | }, 219 | release: function (r) { cached[r].should.be.true; cached[r] = false; } 220 | }, 221 | eyeInstance = new eye({ spawn: spawner.spawn, resourceCache: resourceCache }), 222 | callback = this.callback; 223 | 224 | spawner.once('ready', function () { 225 | spawner.stderr.emit('data', eyeSignature + errorText); 226 | spawner.stdout.emit('data', outputText); 227 | spawner.stdout.emit('end'); 228 | spawner.stderr.emit('end'); 229 | spawner.stdout.listeners('data').should.be.empty; 230 | spawner.stderr.listeners('data').should.be.empty; 231 | spawner.stdout.listeners('end').should.be.empty; 232 | spawner.stderr.listeners('end').should.be.empty; 233 | for (var file in cached) 234 | cached[file].should.be.false; 235 | }); 236 | 237 | eyeInstance.execute(options, function (err, result) { 238 | callback(err, result, spawner); 239 | }); 240 | }; 241 | } 242 | 243 | function shouldExecuteEyeWith(options, description, expectedArgs, eyeOutput, expectedOutput, error, errorMessage) { 244 | var context = { 245 | topic: executeEyeWith(options, error, eyeOutput || "eyeOutput") 246 | }; 247 | 248 | context['should execute EYE ' + description] = function (err, result, spawner) { 249 | spawner.command.should.eql('eye'); 250 | if (typeof(expectedArgs) !== 'function') 251 | spawner.args.should.eql(expectedArgs); 252 | else 253 | expectedArgs(spawner.args); 254 | }; 255 | 256 | if (!error) 257 | context['should return the EYE output'] = function (err, result) { 258 | should.not.exist(err); 259 | result.should.equal(expectedOutput || "eyeOutput"); 260 | }; 261 | else 262 | context['should return the EYE error'] = function (err, result) { 263 | err.should.equal(errorMessage); 264 | should.not.exist(result); 265 | }; 266 | 267 | return context; 268 | } 269 | -------------------------------------------------------------------------------- /test/eyeserver-test.js: -------------------------------------------------------------------------------- 1 | var eyeserver = require('../lib/eyeserver.js'); 2 | var vows = require('vows'), 3 | should = require('should'), 4 | request = require('request'), 5 | EventEmitter = require('events').EventEmitter; 6 | 7 | vows.describe('EyeServer').addBatch({ 8 | 'The eyeserver module': { 9 | topic: function () { return eyeserver; }, 10 | 11 | 'should be a function': function (eyeserver) { 12 | eyeserver.should.be.a('function'); 13 | }, 14 | 15 | 'should make EyeServer objects': function (eyeserver) { 16 | eyeserver().constructor.should.eql(eyeserver); 17 | eyeserver().should.be.an.instanceof(eyeserver); 18 | }, 19 | 20 | 'should be an EyeServer constructor': function (eyeserver) { 21 | new eyeserver().constructor.should.eql(eyeserver); 22 | new eyeserver().should.be.an.instanceof(eyeserver); 23 | } 24 | }, 25 | 26 | 'An EyeServer instance': { 27 | topic: function () { 28 | server = new eyeserver({ eye: eyeDummy, debug: true }); 29 | server.listen(13705); 30 | return server; 31 | }, 32 | 33 | teardown: function () { 34 | server.close(); 35 | }, 36 | 37 | 'receiving a request to /': 38 | respondsWith(200, 'text/n3', 'without data', { data: [], pass: true }), 39 | 40 | 'receiving a request to /?quickAnswer': 41 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: true }), 42 | 43 | 'receiving a request to /?quickanswer': 44 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: true }), 45 | 46 | 'receiving a request to /?quick-answer': 47 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: true }), 48 | 49 | 'receiving a request to /?quickAnswer=0': 50 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: false }), 51 | 52 | 'receiving a request to /?quickanswer=false': 53 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: false }), 54 | 55 | 'receiving a request to /?quick-answer=1': 56 | respondsWith(200, 'text/n3', 'with quickAnswer', { data: [], pass: true, quickAnswer: true }), 57 | 58 | 'receiving a request to /?data=http%3A%2F%2Fex.org%2Ferror': 59 | respondsWith(400, 'text/plain', 'and receive an error', { data: ['http://ex.org/error'], pass: true }), 60 | 61 | 'receiving a request to /?data=http%3A%2F%2Fex.org%2F1': 62 | respondsWith(200, 'text/n3', 'with one URI', { data: ['http://ex.org/1'], pass: true }), 63 | 64 | 'receiving a request to /?data=http%3A%2F%2Fex.org%2F1,http%3A%2F%2Fex.org%2F2': 65 | respondsWith(200, 'text/n3', 'with two URIs', { data: ['http://ex.org/1', 'http://ex.org/2'], pass: true }), 66 | 67 | 'receiving a request to /?data=http%3A%2F%2Fex.org%2F1&data=http%3A%2F%2Fex.org%2F2': 68 | respondsWith(200, 'text/n3', 'with two URIs', { data: ['http://ex.org/1', 'http://ex.org/2'], pass: true }), 69 | 70 | 'receiving a request to /?data=http%3A%2F%2Fex.org%2F1&data=http%3A%2F%2Fex.org%2F2,http%3A%2F%2Fex.org%2F3': 71 | respondsWith(200, 'text/n3', 'with three URIs', { data: ['http://ex.org/1', 'http://ex.org/2', 'http://ex.org/3'], pass: true }), 72 | 73 | 'receiving a request to /?data=%3Aa%20%3Ab%20%3Ac.': 74 | respondsWith(200, 'text/n3', 'with N3 data', { data: [':a :b :c.'], pass: true }), 75 | 76 | 'receiving a request to /?query=http%3A%2F%2Fex.org%2F1': 77 | respondsWith(200, 'text/n3', 'with a query', { data: [], query: 'http://ex.org/1' }), 78 | 79 | // note: adding query parameters to obtain unique URIs for test validation 80 | 'receiving a request to /?f1 with form data=http%3A%2F%2Fex.org%2F1': 81 | respondsWith(200, 'text/n3', 'with one URI', { data: ['http://ex.org/1'], pass: true }, "POST"), 82 | 83 | 'receiving a request to /?f2 with form data=http%3A%2F%2Fex.org%2F1&data=http%3A%2F%2Fex.org%2F2': 84 | respondsWith(200, 'text/n3', 'with two URIs', { data: ['http://ex.org/1', 'http://ex.org/2'], pass: true }, "POST"), 85 | 86 | 'receiving a request to /?f3&data=http%3A%2F%2Fex.org%2F1 with form data=http%3A%2F%2Fex.org%2F2': 87 | respondsWith(200, 'text/n3', 'with two URIs', { data: ['http://ex.org/1', 'http://ex.org/2'], pass: true }, "POST"), 88 | 89 | 'receiving a request to /?f4&data=http%3A%2F%2Fex.org%2F1 with form data=%3Aa%20%3Ab%20%3Ac.': 90 | respondsWith(200, 'text/n3', 'with two URIs', { data: ['http://ex.org/1', ':a :b :c.'], pass: true }, "POST"), 91 | 92 | 'receiving a request to /?f5&data=http%3A%2F%2Fex.org%2F1 with form query=http%3A%2F%2Fex.org%2F2': 93 | respondsWith(200, 'text/n3', 'with a URI and a query', { data: ['http://ex.org/1'], query: 'http://ex.org/2' }, "POST"), 94 | 95 | 'receiving a request to /?callback=mycallback': 96 | respondsWith(200, 'application/javascript', 'without data', { data: [], pass: true }, "GET", 'mycallback("out\\"put")'), 97 | 98 | 'receiving a request to /?callback=my{illegal}callback': 99 | respondsWith(400, 'application/javascript', 'without data', { data: [], pass: true }, "GET", 'alert("Illegal callback name.")'), 100 | 101 | 'receiving an OPTIONS request on /': { 102 | topic: function () { 103 | request({ url: 'http://localhost:13705/', method: 'OPTIONS' }, this.callback); 104 | }, 105 | 106 | 'should respond with Access-Control-Allow-Origin *': function (error, response, body) { 107 | response.headers.should.have.property('access-control-allow-origin', '*'); 108 | } 109 | }, 110 | 111 | 'receiving a request that is aborted': (function () { 112 | var canceled; 113 | return { 114 | topic: function () { 115 | // setup dummy eye with dummy eyeProcess 116 | var eyeProcess = { cancel: function () { canceled = true; } }; 117 | var server = new eyeserver({ eye: { execute: function () { return eyeProcess; } } }); 118 | // set up dummy request 119 | var req = new EventEmitter(); 120 | req.query = {}; 121 | server.handleEyeRequest(req, { header: function () {} }); 122 | // abort request 123 | req.emit('close'); 124 | 125 | return true; 126 | }, 127 | 'should cancel Eye': function () { 128 | should.exist(canceled); 129 | } 130 | }; 131 | })() 132 | } 133 | }).export(module); 134 | 135 | var eyeDummy = { 136 | execute: function (options, callback) { 137 | var path = options.originalUrl; 138 | delete options.originalUrl; 139 | this.options[path] = options; 140 | 141 | if (this.shouldSucceed[path]) 142 | callback(null, 'out"put'); 143 | else 144 | callback('err"or', null); 145 | 146 | return {}; 147 | }, 148 | options: {}, 149 | shouldSucceed: {} 150 | }; 151 | 152 | function respondsWith(status, contentType, description, executeArguments, method, output) { 153 | if (!method) { 154 | return { 155 | 'using GET' : respondsWith(status, contentType, description, executeArguments, "GET"), 156 | 'using POST': respondsWith(status, contentType, description, executeArguments, "POST") 157 | }; 158 | } 159 | 160 | var path, form, shouldSucceed = (status >= 200 && status <= 299); 161 | var context = { 162 | topic: function () { 163 | var urlMatch = this.context.title.match(/(\/[^ ]*)(?: with form (.*))?/); 164 | path = urlMatch[1]; 165 | form = urlMatch[2]; 166 | eyeDummy.shouldSucceed[path] = shouldSucceed; 167 | request({ url: 'http://localhost:13705' + path, 168 | body: form, 169 | headers: { 'content-type': 'application/x-www-form-urlencoded' }, 170 | method: method }, this.callback); 171 | } 172 | }; 173 | 174 | context['should respond with status ' + status] = function (error, response, body) { 175 | response.statusCode.should.eql(status); 176 | }; 177 | 178 | context['should respond with Content-Type ' + contentType] = function (error, response, body) { 179 | response.headers.should.have.property('content-type', contentType); 180 | }; 181 | 182 | if (contentType !== 'application/javascript') 183 | context['should respond with Access-Control-Allow-Origin *'] = function (error, response, body) { 184 | response.headers.should.have.property('access-control-allow-origin', '*'); 185 | }; 186 | 187 | if (shouldSucceed) 188 | context['should return the Eye output'] = function (error, response, body) { 189 | body.should.eql(output || 'out"put\n'); 190 | }; 191 | else 192 | context['should return the Eye error'] = function (error, response, body) { 193 | body.should.eql(output || 'err"or\n'); 194 | }; 195 | 196 | context['should execute Eye ' + description] = function (error, response, body) { 197 | eyeDummy.options[path].should.eql(executeArguments); 198 | }; 199 | 200 | return context; 201 | } 202 | -------------------------------------------------------------------------------- /test/spawnasserter.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | 3 | function SpawnAsserter() { 4 | var spawnAsserter = new events.EventEmitter(); 5 | 6 | spawnAsserter.spawn = function (command, args) { 7 | spawnAsserter.command = command; 8 | spawnAsserter.args = args; 9 | 10 | spawnAsserter.stdout = new events.EventEmitter(); 11 | spawnAsserter.stderr = new events.EventEmitter(); 12 | 13 | spawnAsserter.kill = function () { 14 | spawnAsserter.killed = true; 15 | spawnAsserter.emit('killed'); 16 | }; 17 | 18 | process.nextTick(function () { 19 | spawnAsserter.emit('ready'); 20 | }); 21 | 22 | return spawnAsserter; 23 | }; 24 | 25 | return spawnAsserter; 26 | } 27 | 28 | module.exports = SpawnAsserter; 29 | --------------------------------------------------------------------------------