Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
buildbotService: based on Restangular
buildbotService is a typical restangular object, but adds methods to automatically update scope based on 'sse' events Add unit tests: - EventSourceMock simulates events - httpMock simulates the whole backend, with help from dataspec json info. This will be used to unittest controllers, for E2E tests, and for backendless development add restangular and lodash deps.
- Loading branch information
Pierre Tardy
committed
Aug 3, 2013
1 parent
c496548
commit c353ddd
Showing
12 changed files
with
388 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
angular.module 'app', ['ngResource'] | ||
angular.module 'app', ['restangular'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../components/lodash/lodash.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../components/restangular/src/restangular.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,52 @@ | ||
angular.module('app').factory 'EventSource', -> | ||
# turn HTML5's EventSource into a angular module for mockability | ||
return (url)-> | ||
return new EventSource(url) | ||
BASEURLAPI = 'api/v2/' | ||
BASEURLSSE = 'sse/' | ||
angular.module('app').factory 'buildbotService', | ||
['$log', '$resource', | ||
($log, $resource) -> | ||
# populateScope populate $scope[scope_key] with an api_query use | ||
# sse_query and EventSource to update automatically the table | ||
['$log', 'Restangular', 'EventSource', | ||
($log, Restangular, EventSource) -> | ||
configurer = (RestangularConfigurer) -> | ||
onElemRestangularized = (elem, isCollection, route, Restangular) -> | ||
# add the bind() method to each restangular object | ||
# bind method will create one way binding (readonly) | ||
# via event source | ||
elem.bind = ($scope, scope_key) -> | ||
if not scope_key? | ||
scope_key = elem.route | ||
if (isCollection) | ||
onEvent = (e) -> | ||
$scope[scope_key].push(e.msg) | ||
$scope.$apply() | ||
$scope[scope_key] = elem.getList() | ||
$scope[scope_key].then -> | ||
elem.on("new", onEvent) | ||
else | ||
onEvent = (e) -> | ||
for k, v of e.msg | ||
$scope[scope_key][k] = v | ||
$scope.$apply() | ||
$scope[scope_key] = this.get() | ||
$scope[scope_key].then -> | ||
elem.on("update", onEvent) | ||
$scope.$on("$destroy", -> elem.source.close()) | ||
return $scope[scope_key] | ||
|
||
# @todo the implementation is very naive for now. Need to sort out | ||
# server side paging/sorting (do we really need serverside sorting?) | ||
elem.unbind = () -> | ||
this.source?.close() | ||
|
||
elem.on = (event, onEvent) -> | ||
if not elem.source? | ||
route = elem.getRestangularUrl() | ||
route = route.replace(BASEURLAPI, BASEURLSSE) | ||
source = new EventSource(route) | ||
elem.source = source | ||
elem.source.addEventListener(event, onEvent) | ||
return elem | ||
RestangularConfigurer.setBaseUrl(BASEURLAPI) | ||
RestangularConfigurer.setOnElemRestangularized(onElemRestangularized) | ||
|
||
return Restangular.withConfig(configurer) | ||
|
||
populateScope = ($scope, scope_key, api_query, sse_query) -> | ||
$scope[scope_key] = $resource("api/v2/"+api_query).query() | ||
source = new EventSource("sse/"+sse_query) | ||
source.addEventListener "event", (e) -> | ||
$scope[scope_key].push msg | ||
$scope.$apply() | ||
{populateScope} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
window.mockEventSource = -> | ||
mocked = (url) -> | ||
class EventSourceMock | ||
|
||
constructor: (@url) -> | ||
this.readyState = 1 # directly connect | ||
this.onEvent = {} | ||
|
||
addEventListener: (event, cb) -> | ||
if not this.onEvent.hasOwnProperty(event) | ||
this.onEvent[event] = [] | ||
this.onEvent[event].push(cb) | ||
|
||
fakeEvent: (eventtype, event) -> | ||
if this.onEvent.hasOwnProperty(eventtype) | ||
for cb in this.onEvent[eventtype] | ||
cb(event) | ||
|
||
close: -> | ||
this.readyState = 2 | ||
|
||
return new EventSourceMock(url) | ||
|
||
# overrride "EventSource" | ||
beforeEach module(($provide) -> | ||
$provide.value("EventSource", mocked) | ||
null # those module callbacks need to return null! | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,90 @@ | ||
beforeEach module 'app' | ||
|
||
beforeEach -> | ||
this.addMatchers { toEqualData: (expected) -> | ||
angular.equals this.actual, expected } | ||
|
||
describe 'buildbot service', -> | ||
mockEventSource() | ||
buildbotService = {} | ||
$httpBackend = {} | ||
EventSourceMock = {} | ||
$scope = {} | ||
beforeEach inject (_$httpBackend_, $injector) -> | ||
$httpBackend = _$httpBackend_ | ||
$httpBackend.expectGET('api/v2/changes') | ||
.respond [] | ||
$httpBackend = {} | ||
|
||
injected = ($injector) -> | ||
$httpBackend = $injector.get('$httpBackend') | ||
$scope = $injector.get('$rootScope').$new() | ||
buildbotService = $injector.get('buildbotService') | ||
|
||
it 'should query for changes at /changes and receive an array', -> | ||
beforeEach(inject(injected)) | ||
|
||
it 'should query for changes at /changes and receive an empty array', -> | ||
$httpBackend.expectGET('api/v2/changes').respond([]) | ||
p = buildbotService.all("changes").bind($scope, "changes") | ||
p.then((res) -> | ||
expect(res.length).toBe(0) | ||
) | ||
$httpBackend.flush() | ||
|
||
it 'should query for build/1/step/2 and receive a SUCCESS result', -> | ||
$httpBackend.expectGET('api/v2/build/1/step/2').respond({res: "SUCCESS"}) | ||
r = buildbotService.one("build", 1).one("step", 2) | ||
p = r.bind($scope, "step_scope") | ||
p.then((res) -> | ||
expect(res.res).toBe("SUCCESS") | ||
) | ||
$httpBackend.flush() | ||
|
||
it 'should query default scope_key to route key', -> | ||
$httpBackend.expectGET('api/v2/build/1/step/2').respond({res: "SUCCESS"}) | ||
p = buildbotService.one("build", 1).one("step", 2).bind($scope) | ||
expect($scope.step).toBe(p) | ||
$httpBackend.flush() | ||
expect($scope.step).toBe(p) | ||
|
||
it 'should close the eventsource on scope.$destroy()', -> | ||
$httpBackend.expectGET('api/v2/build/1/step/2').respond({res: "SUCCESS"}) | ||
r = buildbotService.one("build", 1).one("step", 2) | ||
p = r.bind($scope) | ||
expect($scope.step).toBe(p) | ||
$httpBackend.flush() | ||
expect(r.source.readyState).toBe(1) | ||
$scope.$destroy() | ||
expect(r.source.readyState).toBe(2) | ||
|
||
it 'should close the eventsource on unbind()', -> | ||
$httpBackend.expectGET('api/v2/build/1/step/2').respond({res: "SUCCESS"}) | ||
r = buildbotService.one("build", 1).one("step", 2) | ||
p = r.bind($scope) | ||
expect($scope.step).toBe(p) | ||
$httpBackend.flush() | ||
expect(r.source.readyState).toBe(1) | ||
r.unbind() | ||
expect(r.source.readyState).toBe(2) | ||
|
||
it 'should update the $scope when event received', -> | ||
$httpBackend.expectGET('api/v2/build/1/step/2').respond({res: "PENDING", otherfield: "FOO"}) | ||
r = buildbotService.one("build", 1).one("step", 2) | ||
p = r.bind($scope) | ||
expect($scope.step).toBe(p) | ||
p.then((res) -> | ||
$scope.step=res # this is done automatically by ng in real environment but not in test | ||
) | ||
$httpBackend.flush() | ||
expect(r.source.url).toBe("sse/build/1/step/2") | ||
expect($scope.step.res).toBe("PENDING") | ||
r.source.fakeEvent("update", {event: "update", msg: {res: "SUCCESS"}}) | ||
expect($scope.step.res).toBe("SUCCESS") | ||
# should not override other fields | ||
expect($scope.step.otherfield).toBe("FOO") | ||
|
||
buildbotService.populateScope $scope, "changes", "changes", "changes" | ||
# $httpBackend.flush() | ||
it 'should update the $scope when event received for collections', -> | ||
$httpBackend.expectGET('api/v2/build/1/step').respond([]) | ||
r = buildbotService.one("build", 1).all("step") | ||
p = r.bind($scope) | ||
expect($scope.step).toBe(p) | ||
p.then((res) -> | ||
$scope.step=res # this is done automatically by ng in real environment but not in test | ||
) | ||
$httpBackend.flush() | ||
expect(r.source.url).toBe("sse/build/1/step") | ||
expect($scope.step.length).toBe(0) | ||
r.source.fakeEvent("new", {event: "new", msg: {res: "SUCCESS"}}) | ||
expect($scope.step.length).toBe(1) | ||
expect($scope.step[0].res).toBe("SUCCESS") |
Oops, something went wrong.