diff --git a/app/services/registry-services.js b/app/services/registry-services.js index ed4f843..dc466ec 100644 --- a/app/services/registry-services.js +++ b/app/services/registry-services.js @@ -7,6 +7,53 @@ // https://docs.angularjs.org/tutorial/step_11 // https://docs.angularjs.org/api/ngResource/service/$resource +function getNextLink(linkHeader) { + return linkHeader.split(',').filter(function(l){ + return /rel="next"/.test(l); + })[0]; +} + +function getURL(link) { + var regex = /<(.+)>;/g; + var url = link ? regex.exec(link) : undefined; + return url ? url[1] : undefined; +} + +function getLast(link) { + var regex = /last=(.+)&/g; + return link ? regex.exec(link)[1] : undefined; +} + +/** + * Extract the "last=" part from Link header: + * + * Link: ; rel="next" + * + * We only want to extract the "last" part and store it like this + * + * lastNamespace = namespace + * lastRepository = repository + **/ +function linkParser(linkHeader) { + var namespace; + var repository; + var last; + var link; + var url; + var parts; + + if (linkHeader) { + link = getNextLink(linkHeader) + url = getURL(link) + last = getLast(url); + parts = last ? last.split('%2F') : []; + namespace = parts[0]; + repository = parts[1]; + } + + return { namespace: namespace, repository: repository }; +} + angular.module('registry-services', ['ngResource']) .factory('RegistryHost', ['$resource', function($resource){ return $resource('registry-host.json', {}, { @@ -40,46 +87,11 @@ angular.module('registry-services', ['ngResource']) isArray: false, transformResponse: function(data, headers){ var repos = angular.fromJson(data).repositories; - - // Extract the "last=" part from Link header: - // - // Link: ; rel="next" - // - // We only want to extrace the "last" part and store it like this - // - // lastNamespace = namespace - // lastRepository = repository - // - // TODO: Can we clean this up a bit? - var last = undefined; - var lastNamespace = undefined; - var lastRepository = undefined; - var linkHeader = headers()['link']; - //console.log('linkHeader='+linkHeader); - if (linkHeader) { - var lastUrl = ''+linkHeader.split(';')[0].replace('<','').replace('>',''); - var startPos = lastUrl.search('last='); - //console.log('startPos=' + startPos); - if (startPos >= 0) { - var endPos = lastUrl.substring(startPos).search('&'); - //console.log('endPos=' + endPos); - if (endPos >= 0) { - last = lastUrl.substring(startPos+'last='.length, startPos+endPos); - //console.log('last=' + last); - var parts = last.split('%2F'); - //console.log('parts=' + parts); - if (parts.length == 2) { - lastNamespace = parts[0]; - lastRepository = parts[1]; - } - } - } - } - + var last = linkParser(headers()['link']) var ret = { repos: [], - lastNamespace: lastNamespace, - lastRepository: lastRepository + lastNamespace: last.namespace, + lastRepository: last.repository }; angular.forEach(repos, function(value/*, key*/) { diff --git a/app/services/registry-services.spec.js b/app/services/registry-services.spec.js new file mode 100644 index 0000000..c0cef07 --- /dev/null +++ b/app/services/registry-services.spec.js @@ -0,0 +1,187 @@ +'use strict'; + +describe('registry-service', function () { + var mockRepository, $httpBackend; + beforeEach(angular.mock.module('registry-services')); + + describe('GET catalog', function() { + beforeEach(function () { + angular.mock.inject(function ($injector) { + $httpBackend = $injector.get('$httpBackend'); + mockRepository = $injector.get('Repository'); + }); + }); + + describe('when no last given', function(){ + it('should return repositories', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond(200, { + repositories: ['nginx'] + }); + + var result = mockRepository.query({ n: 20 }); + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'nginx', + name: 'nginx', + selected: false + }); + expect(result.lastNamespace).toBeUndefined; + expect(result.lastRepository).toBeUndefined; + })); + }); + + describe('when last given', function(){ + it('should use last in query pram', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=nginx') + .respond({ + repositories: ['redis'] + }); + + var result = mockRepository.query({ n: 20, last: 'nginx' }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toBeUndefined; + expect(result.lastRepository).toBeUndefined; + })); + }); + + describe('when only the first page is fetched', function(){ + describe('and last parameter has namespace and repo name', function(){ + it('should return last properties', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond({ + repositories: ['redis'] + }, { + link: '; rel="next"' + }); + + var result = mockRepository.query({ n: 20 }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toEqual('namespace'); + expect(result.lastRepository).toEqual('repository'); + })); + }); + + describe('and last parameter has only repo name', function(){ + it('should return last properties', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond({ + repositories: ['redis'] + }, { + link: '; rel="next"' + }); + + var result = mockRepository.query({ n: 20 }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toEqual('repository'); + expect(result.lastRepository).toBeUndefined; + })); + }); + + describe('and no next link is provided', function(){ + it('should return undefined last properties', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond({ + repositories: ['redis'] + }, { + link: '; rel="not-next"' + }); + + var result = mockRepository.query({ n: 20 }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toBeUndefined; + expect(result.lastRepository).toBeUndefined; + })); + }); + + describe('and next link malformed', function(){ + it('should return undefined last properties', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond({ + repositories: ['redis'] + }, { + link: '/v2/_catalog?last=namespace%2Frepository&n=10; rel="next"' + }); + + var result = mockRepository.query({ n: 20 }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toBeUndefined; + expect(result.lastRepository).toBeUndefined; + })); + }); + + describe('and next link missing last parameter', function(){ + it('should return undefined last properties', inject(function () { + $httpBackend.expectGET('/v2/_catalog?n=20&last=') + .respond({ + repositories: ['redis'] + }, { + link: '/v2/_catalog?n=10; rel="next"' + }); + + var result = mockRepository.query({ n: 20 }); + + $httpBackend.flush(); + + expect(result).hasOwnProperty('repos'); + expect(result.repos.length).toEqual(1); + expect(result.repos[0]).toEqual({ + username: 'redis', + name: 'redis', + selected: false + }); + expect(result.lastNamespace).toBeUndefined; + expect(result.lastRepository).toBeUndefined; + })); + }); + }); + }); +});