Yesterday, I taught my comet initialization fab.js middleware to ignore upstream connection closes (closing a connection is not of much use when doing comet). Today, I need to teach the upstream app,
player_from_querystring
, to close connections properly.I have the functionality of building a player object from a query string working quite well. Not coincidentally, it is well tested with vows.js. The remaining code, dealing with invalid query strings and telling downstream that we are done talking looks like:
function player_from_querystring() {Before doing anything else, I am eliminating the distinction between the
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
// Build a (q)uerystring object and generate a uniq_id
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });
if ( app ) app();
}
else {
out();
}
};
}
out
and app
local variables. The out
local variable is, by convention, the downstream (close to the browser) listener in fab.js. When I call it with an object representation of the player, I send that object back downstream (middleware decorates it in a browser-friendly way). The downstream listener returns another listener, which I assign to the local variable app
, in case the current app has any more to say. Here, there is nothing more to say, so I call app()
with empty arguments. The thing is,
out
and app
refer to the same downstream listener. The last thing the out
listener does is return a reference to itself. I suppose another listener could be returned, but in my experience it is the always the same listener. Even if it were not the same listener, from the current app's perspective, it is still the downstream listener, so why not re-use the out
variable which, by fab.js convention, refers to the downstream:out = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });Much better, now my brain does not begin to wonder what
if (out) out();
app
is. There is only out
and I know what that is.Now to actually drive the new functionality. My vows.js test currently report:
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/player_from_querystring_test.jsThat last test is now wrong. Instead of expecting a null response when data is POSTed to this resource, I should expect a 4xx HTTP error. Additionally, I should expect to that an invalid querystring (i.e. without required fields) should also result in a 4xx HTTP error. For now, I will mark the invalid querystring topic as pending and will fix the "POSTing data" topic / test:
♢ player_from_querystring
with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
without explicit X-Y coordinates
✓ has X coordinate
✓ has Y coordinate
POSTing data
✓ is null response
✓ OK » 7 honored (0.103s)
'POSTing data': {With that, I get my expected failure:
topic: api.fab.response_to({body: "foo"}),
'should raise an error': function (obj) {
assert.equal(obj.status, 406);
}
},
'invalid querystring': "pending"
// topic: api.fab.response_to({
// url: { search : "?foo=bar" },
// headers: { cookie: null }
// }),
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/player_from_querystring_test.jsAahhh! I am in a new testing framework and a relatively new application framework, but my development cycle fits like an old glove. Change the message or make it pass. Here, I can move right onto the make it pass state with:
♢ player_from_querystring
- invalid querystring
with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
without explicit X-Y coordinates
✓ has X coordinate
✓ has Y coordinate
POSTing data
✗ should raise an error
TypeError: Cannot read property 'status' of undefined
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test/player_from_querystring_test.js:73:25)
at runTest (/home/cstrom/.node_libraries/.npm/vows/0.4.5/package/lib/vows.js:99:26)
at EventEmitter.<anonymous> (/home/cstrom/.node_libraries/.npm/vows/0.4.5/package/lib/vows.js:72:9)
at EventEmitter.emit (events:42:20)
at /home/cstrom/.node_libraries/.npm/vows/0.4.5/package/lib/vows/context.js:24:44
at EventEmitter._tickCallback (node.js:48:25)
at node.js:204:9
✗ Errored » 6 honored ∙ 1 errored ∙ 1 pending (0.117s)
function player_from_querystring() {And yup, that passes:
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
// Build a (q)uerystring object and generate a uniq_id
var out = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });
if (out) out();
}
else {
out({ status: 406 });
}
};
}
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/player_from_querystring_test.jsNow I move onto driving the behavior when invalid query strings are supplied. The test topic sends an invalid query string in the header. The expectation for that topic is that the status returned will be 406 (Invalid Format Request):
♢ player_from_querystring
- invalid querystring
with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
without explicit X-Y coordinates
✓ has X coordinate
✓ has Y coordinate
POSTing data
✓ should raise an error
✓ OK » 7 honored ∙ 1 pending (0.106s)
'invalid querystring': {Running this test, it currently fails (as expected):
topic: api.fab.response_to({
url: { search : "?foo=bar" },
headers: { cookie: null }
}),
'should raise an error': function (obj) {
assert.equal(obj.status, 406);
}
}
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/player_from_querystring_test.jsI make it pass by adding a conditional ensuring that a player attribute is present:
♢ player_from_querystring
with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
without explicit X-Y coordinates
✓ has X coordinate
✓ has Y coordinate
POSTing data
✓ should raise an error
invalid querystring
✗ should raise an error
» expected 406,
got undefined (==) // player_from_querystring_test.js:82
✗ Broken » 7 honored ∙ 1 broken (0.121s)
function player_from_querystring() {With that, I have both of my new tests passing:
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
// Build a (q)uerystring object and generate a uniq_id
if (q.player) {
out = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0, uniq_id: uniq_id} });
out();
}
else {
out = out({ status: 406 });
}
}
else {
out({ status: 406 });
}
};
}
cstrom@whitefall:~/repos/my_fab_game$ vows --spec test/player_from_querystring_test.jsBefore calling it a day, I smoke test both of these new options along with a valid player via
♢ player_from_querystring
with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
without explicit X-Y coordinates
✓ has X coordinate
✓ has Y coordinate
POSTing data
✓ should raise an error
invalid querystring
✓ should raise an error
✓ OK » 8 honored (0.100s)
curl
:# Fails with POSTed dataPerfect!
cstrom@whitefall:~/repos/my_fab_game$ curl -i http://localhost:4011/comet_view -d foo=bar
HTTP/1.1 406 Not Acceptable
Connection: keep-alive
Transfer-Encoding: chunked
# Fails with an invalid querystring
cstrom@whitefall:~/repos/my_fab_game$ curl -i http://localhost:4011/comet_view?foo=bar
HTTP/1.1 406 Not Acceptable
Connection: keep-alive
Transfer-Encoding: chunked
# Success!
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/comet_view?player=foo\&x=250\&y=350
<html><body>
<script type="text/javascript">window.parent.player_list.new_player({"id":"foo","x":250,"y":350,"uniq_id":"322f2ded412c1f5161876535ce441688"})</script>
That is a good stopping place for today. Up tomorrow: perhaps some refactoring or moving on to other aspects of my (fab) game.
Day #163
No comments:
Post a Comment