diff --git a/src/keystone.js b/src/keystone.js index f678f7c..583c0b7 100644 --- a/src/keystone.js +++ b/src/keystone.js @@ -7,7 +7,7 @@ import AbstractService from './util/abstractService'; * @ignore */ const supportedKeystoneVersions = [ - 'v3.7' + 'v3.1' ]; export default class Keystone extends AbstractService { @@ -59,12 +59,13 @@ export default class Keystone extends AbstractService { } /** - * Retrieve all the API versions available. + * Retrieve all the raw API versions available. * - * @returns {Promise.} A promise that will resolve with the list of API versions. + * @returns {Promise.} A promise that will resolve with the list of raw versions. + * @protected */ - versions() { - return super.versions() + _rawVersions() { + return super._rawVersions() .then((versions) => versions.values); } diff --git a/src/util/abstractService.js b/src/util/abstractService.js index 517f9e1..501b5e5 100644 --- a/src/util/abstractService.js +++ b/src/util/abstractService.js @@ -15,6 +15,7 @@ */ import Http from './http'; +import Version from './version'; import URL from 'url-parse'; export default class AbstractService { @@ -64,9 +65,25 @@ export default class AbstractService { /** * Retrieve all the API versions available. * - * @returns {Promise.} A promise that will resolve with the list of API versions. + * @returns {Promise.} A promise that will resolve with the list of API versions. */ versions() { + return this._rawVersions().then((versions) => { + return versions.map((rawVersion) => { + const version = new Version(rawVersion.id); + version.links = rawVersion.links; + return version; + }); + }); + } + + /** + * Retrieve all the raw API versions available. + * + * @returns {Promise.} A promise that will resolve with the list of raw versions. + * @protected + */ + _rawVersions() { return new Promise((resolve, reject) => { let promise = this.http .httpGet(this._endpointUrl) @@ -92,14 +109,14 @@ export default class AbstractService { /** * Retrieve the API version declaration that is currently in use by this instance. * - * @returns {Promise.} A promise that will resolve with the specific API version. + * @returns {Promise.} A promise that will resolve with the specific API version. */ version() { return this .versions() .then((versions) => { for (let version of versions) { - if (this.supportedVersions.indexOf(version.id) > -1) { + if (this.supportedVersions.find(version.supports)) { return version; } } diff --git a/src/util/version.js b/src/util/version.js index cb92cd2..2dc1ad4 100644 --- a/src/util/version.js +++ b/src/util/version.js @@ -56,10 +56,30 @@ export default class Version { return this._patch || 0; } + /** + * The links of the service + * + * @returns {Object[]} The list of links. + */ + get links() { + return this._links || null; + } + + /** + * Sets the links of the service + * + * @param {Object[]} links The links to be set + */ + set links(links) { + if (Array.isArray(links)) { + this._links = links; + } + } + /** * Create a new instance of a service version. * - * @param {String} service The name of the service. + * @param {String} [service] The name of the service. * @param {String} versionString The version string for this service. */ constructor(service, versionString) { @@ -89,6 +109,10 @@ export default class Version { this._minor = parseInt(results[6], 10); this._patch = parseInt(results[8], 10); } + this._links = null; + + this.equals = this.equals.bind(this); + this.supports = this.supports.bind(this); } /** @@ -112,4 +136,38 @@ export default class Version { version.patch === this.patch && version.service === this.service; } + + /** + * Verifies compatibility of this instance to another instance. Major version should be equal and + * minor version should be greater or equal than `version` parameter. + * + * @param {String|Version} version the version to support. + * @returns {boolean} True if the version is compatible, otherwise false + */ + supports(version) { + if (!(version instanceof Version)) { + if (typeof version === 'string') { + version = new Version(version); + } else { + return false; + } + } + + const compatibleVersion = version.service === this.service && + version.major === this.major && + version.minor <= this.minor; + + if (compatibleVersion && version.minor === this.minor) { + return version.patch <= this.patch; + } + return compatibleVersion; + } + + toString() { + let version = `${this.major}.${this.minor}`; + if (this.patch) { + version = `${version}.${this.patch}`; + } + return version; + } } diff --git a/test/functional/glanceTest.js b/test/functional/glanceTest.js index e64be7f..d6b00a5 100644 --- a/test/functional/glanceTest.js +++ b/test/functional/glanceTest.js @@ -50,7 +50,7 @@ describe("Glance", () => { describe("version()", () => { const supportedApiVersions = [ - new Version('image 2.3') + new Version('2.4') ]; /** @@ -61,11 +61,7 @@ describe("Glance", () => { configPromise .then((config) => new Glance(config)) .then((glance) => glance.version()) - .then((version) => { - - // Quick sanity check. - const apiVersion = new Version('image', version.id); - + .then((apiVersion) => { for (let i = 0; i < supportedApiVersions.length; i++) { let supportedVersion = supportedApiVersions[i]; if (apiVersion.equals(supportedVersion)) { @@ -73,7 +69,7 @@ describe("Glance", () => { return; } } - fail("Current devstack glance version is not supported."); + fail(`Current devstack glance version (${apiVersion}) is not supported.`); done(); }) .catch((error) => done.fail(error)); diff --git a/test/functional/keystoneTest.js b/test/functional/keystoneTest.js index 93ac168..c759301 100644 --- a/test/functional/keystoneTest.js +++ b/test/functional/keystoneTest.js @@ -43,7 +43,7 @@ describe("Keystone", () => { describe("version()", () => { const supportedApiVersions = [ - new Version('identity 3.7') + new Version('3.7') ]; /** @@ -52,11 +52,7 @@ describe("Keystone", () => { */ it("should return a supported version.", (done) => { keystone.version() - .then((version) => { - - // Quick sanity check. - const apiVersion = new Version('identity', version.id); - + .then((apiVersion) => { for (let i = 0; i < supportedApiVersions.length; i++) { let supportedVersion = supportedApiVersions[i]; if (apiVersion.equals(supportedVersion)) { @@ -64,7 +60,7 @@ describe("Keystone", () => { return; } } - fail("Current devstack keystone version is not supported."); + fail(`Current devstack keystone version (${apiVersion}) is not supported.`); done(); }) .catch((response) => response.json() diff --git a/test/functional/neutronTest.js b/test/functional/neutronTest.js index 7deee5f..652499b 100644 --- a/test/functional/neutronTest.js +++ b/test/functional/neutronTest.js @@ -50,7 +50,7 @@ describe("neutron", () => { describe("version()", () => { const supportedApiVersions = [ - new Version('network 2.0') + new Version('2.0') ]; /** @@ -61,11 +61,7 @@ describe("neutron", () => { configPromise .then((config) => new Neutron(config)) .then((neutron) => neutron.version()) - .then((version) => { - - // Quick sanity check. - const apiVersion = new Version('network', version.id); - + .then((apiVersion) => { for (let i = 0; i < supportedApiVersions.length; i++) { let supportedVersion = supportedApiVersions[i]; if (apiVersion.equals(supportedVersion)) { @@ -73,7 +69,7 @@ describe("neutron", () => { return; } } - fail("Current devstack neutron version is not supported."); + fail(`Current devstack neutron version (${apiVersion}) is not supported.`); done(); }) .catch((error) => done.fail(error)); diff --git a/test/unit/glanceTest.js b/test/unit/glanceTest.js index f3f794d..898b623 100644 --- a/test/unit/glanceTest.js +++ b/test/unit/glanceTest.js @@ -31,21 +31,6 @@ describe('Glance', () => { expect(() => new Glance()).toThrow(); }); - describe("version()", () => { - it("Should return a supported version of the glance API.", (done) => { - const glance = new Glance(mockData.config); - - fetchMock.mock(mockData.root()); - - glance.version() - .then((version) => { - expect(version.id).toEqual('v2.3'); - done(); - }) - .catch((error) => done.fail(error)); - }); - }); - describe("serviceEndpoint()", () => { it("Should return a valid endpoint to the glance API.", (done) => { const glance = new Glance(mockData.config); diff --git a/test/unit/helpers/data/versions.js b/test/unit/helpers/data/versions.js index ec6a43e..e637dd3 100644 --- a/test/unit/helpers/data/versions.js +++ b/test/unit/helpers/data/versions.js @@ -75,16 +75,6 @@ function rootResponse() { } ] }, - { - status: "SUPPORTED", - id: "v2.0", - links: [ - { - href: `${rootUrl}v2/`, - rel: "self" - } - ] - }, { status: "SUPPORTED", id: "v1.1", diff --git a/test/unit/keystoneTest.js b/test/unit/keystoneTest.js index 647cd61..e21f458 100644 --- a/test/unit/keystoneTest.js +++ b/test/unit/keystoneTest.js @@ -15,43 +15,6 @@ describe('Keystone', () => { expect(() => new Keystone()).toThrow(); }); - describe("versions()", () => { - - /** - * Keystone needs an explicit test, as it uses a slightly different data format - * than other services. - */ - it("Should return a list of all versions available on this clouds' keystone", (done) => { - const keystone = new Keystone(mockData.config); - - fetchMock.mock(mockData.root()); - - keystone.versions() - .then((versions) => { - // Quick sanity check. - expect(versions.length).toBe(2); - done(); - }) - .catch((error) => done.fail(error)); - }); - }); - - describe("version()", () => { - - it("Should return a supported version of the keystone API.", (done) => { - const keystone = new Keystone(mockData.config); - - fetchMock.mock(mockData.root()); - - keystone.version() - .then((version) => { - expect(version.id).toEqual('v3.7'); - done(); - }) - .catch((error) => done.fail(error)); - }); - }); - describe("serviceEndpoint()", () => { it("Should return a valid endpoint to the keystone API.", (done) => { const keystone = new Keystone(mockData.config); diff --git a/test/unit/neutronTest.js b/test/unit/neutronTest.js index 2c802fa..e31f592 100644 --- a/test/unit/neutronTest.js +++ b/test/unit/neutronTest.js @@ -36,22 +36,6 @@ describe('neutron', () => { } }); - describe("versions()", () => { - it("Should return a list of all versions available on this clouds' NEUTRON", (done) => { - const neutron = new Neutron(mockData.config); - - fetchMock.mock(mockData.root()); - - neutron.versions() - .then((versions) => { - // Quick sanity check. - expect(versions.length).toBe(1); - done(); - }) - .catch((error) => done.fail(error)); - }); - }); - describe("networkList()", () => { let neutron = null; diff --git a/test/unit/util/abstractServiceTest.js b/test/unit/util/abstractServiceTest.js index d819fc0..99f74bb 100644 --- a/test/unit/util/abstractServiceTest.js +++ b/test/unit/util/abstractServiceTest.js @@ -53,21 +53,12 @@ describe('AbstractService', () => { service.versions() .then((versions) => { // Quick sanity check. - expect(versions.length).toBe(6); - done(); - }) - .catch((error) => done.fail(error)); - }); - - it("Should return a list of all versions available from this resource", (done) => { - const service = new AbstractService(mockData.rootUrl, mockData.versions); - - fetchMock.mock(mockData.rootResponse()); - - service.versions() - .then((versions) => { - // Quick sanity check. - expect(versions.length).toBe(6); + expect(versions.length).toBe(5); + expect(versions[0].major).toEqual(2); + expect(versions[0].minor).toEqual(3); + expect(versions[0].patch).toEqual(0); + expect(versions[0].links).not.toBe(null); + expect(versions[0].links[0].href).toEqual('http://example.com/v2/'); done(); }) .catch((error) => done.fail(error)); @@ -84,7 +75,7 @@ describe('AbstractService', () => { service.versions() .then((versions) => { // Quick sanity check. - expect(versions.length).toBe(6); + expect(versions.length).toBe(5); done(); }) .catch((error) => done.fail(error)); @@ -132,7 +123,22 @@ describe('AbstractService', () => { service.version() .then((version) => { - expect(version.id).toEqual('v2.3'); + expect(version.equals('v2.3')).toBe(true); + done(); + }) + .catch((error) => done.fail(error)); + }); + + it("Should return the latest compatible version of the service API.", (done) => { + const service = new AbstractService(mockData.rootUrl, [ + 'v2.0' + ]); + + fetchMock.mock(mockData.rootResponse()); + + service.version() + .then((version) => { + expect(version.equals('v2.3')).toBe(true); done(); }) .catch((error) => done.fail(error)); diff --git a/test/unit/util/versionTest.js b/test/unit/util/versionTest.js index e23f518..7f73b88 100644 --- a/test/unit/util/versionTest.js +++ b/test/unit/util/versionTest.js @@ -96,4 +96,63 @@ describe('Version', () => { // Other tests... expect(v2.equals({})).toBe(false); }); + + it("should test for correct compatibility", () => { + const v1 = new Version("compute", "1.3.2"); + + // String tests + expect(v1.supports("compute 1.0.0")).toBe(true); + expect(v1.supports("compute 1.0.1")).toBe(true); + expect(v1.supports("compute 1.3.0")).toBe(true); + expect(v1.supports("compute 1.3.3")).toBe(false); + expect(v1.supports("compute 1.4.0")).toBe(false); + expect(v1.supports("compute 2.3.0")).toBe(false); + + // Version tests + expect(v1.supports(new Version("compute", "1.0.0"))).toBe(true); + expect(v1.supports(new Version("compute", "1.0.1"))).toBe(true); + expect(v1.supports(new Version("compute", "1.3.0"))).toBe(true); + expect(v1.supports(new Version("compute", "1.3.3"))).toBe(false); + expect(v1.supports(new Version("compute", "1.4.0"))).toBe(false); + expect(v1.supports(new Version("compute", "2.3.0"))).toBe(false); + + const v2 = new Version("1.3.2"); + // String tests + expect(v2.supports("1.0.0")).toBe(true); + expect(v2.supports("1.0.1")).toBe(true); + expect(v2.supports("1.3.0")).toBe(true); + expect(v2.supports("1.3.3")).toBe(false); + expect(v2.supports("1.4.0")).toBe(false); + expect(v2.supports("2.3.0")).toBe(false); + + // Version tests + expect(v2.supports(new Version("1.0.0"))).toBe(true); + expect(v2.supports(new Version("1.0.1"))).toBe(true); + expect(v2.supports(new Version("1.3.0"))).toBe(true); + expect(v2.supports(new Version("1.3.3"))).toBe(false); + expect(v2.supports(new Version("1.4.0"))).toBe(false); + expect(v2.supports(new Version("2.3.0"))).toBe(false); + }); + + it("should store links", () => { + const v1 = new Version("compute", "1.3.2"); + + expect(v1.links).toBe(null); + + v1.links = 'wrong data'; + expect(v1.links).toBe(null); + + v1.links = [ + { + href: `http://example.org/v2/`, + rel: "self" + } + ]; + expect(v1.links).not.toBe(null); + expect(v1.links.length).toBe(1); + expect(v1.links[0]).toEqual({ + href: "http://example.org/v2/", + rel: "self" + }); + }); });