From b697e114a3f816985525b7b0684559ece8e75eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Sat, 29 Jan 2022 16:06:52 +0100 Subject: [PATCH] fix: add support for semver ranges --- package.json | 4 +- src/strategy/strategy.ts | 25 ++++++++++ test/strategy/strategy-test.js | 88 ++++++++++++++++++++++++++++++++++ yarn.lock | 5 ++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6175ffa7..532670cf 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "ip": "^1.1.5", "make-fetch-happen": "^9.0.4", "murmurhash3js": "^3.0.1", - "nodemon": "^2.0.15" + "nodemon": "^2.0.15", + "semver": "^7.3.5" }, "engines": { "node": ">=10", @@ -47,6 +48,7 @@ "@types/make-fetch-happen": "9.0.1", "@types/murmurhash3js": "3.0.2", "@types/node": "14.18.9", + "@types/semver": "^7.3.9", "@typescript-eslint/eslint-plugin": "5.10.0", "@unleash/client-specification": "4.0.0", "ava": "3.15.0", diff --git a/src/strategy/strategy.ts b/src/strategy/strategy.ts index 4a9a7222..f58287bf 100644 --- a/src/strategy/strategy.ts +++ b/src/strategy/strategy.ts @@ -1,3 +1,4 @@ +import { gt as semverGt, lt as semverLt, eq as semverEq } from 'semver'; import { Context } from '../context'; import { resolveContextValue } from '../helpers'; @@ -29,6 +30,9 @@ export enum Operator { NUM_LTE = 'NUM_LTE', DATE_AFTER = 'DATE_AFTER', DATE_BEFORE = 'DATE_BEFORE', + SEMVER_EQ = 'SEMVER_EQ', + SEMVER_GT = 'SEMVER_GT', + SEMVER_LT = 'SEMVER_LT', } export type OperatorImpl = (constraint: Constraint, context: Context) => boolean; @@ -69,6 +73,24 @@ const StringOperator = (constraint: Constraint, context: Context) => { return false; } +const SemverOperator = (constraint: Constraint, context: Context) => { + const { contextName, operator} = constraint; + const value = constraint.value as string; + const contextValue = resolveContextValue(context, contextName); + if(!contextValue) { + return false; + } + + if(operator === Operator.SEMVER_EQ) { + return semverEq(contextValue, value); + } if(operator === Operator.SEMVER_LT) { + return semverLt(contextValue, value); + } if(operator === Operator.SEMVER_GT) { + return semverGt(contextValue, value); + } + return false; +} + const DateOperator = (constraint: Constraint, context: Context) => { const { operator } = constraint; const value = constraint.value as Date; @@ -119,6 +141,9 @@ operators.set(Operator.NUM_GT, NumberOperator); operators.set(Operator.NUM_GTE, NumberOperator); operators.set(Operator.DATE_AFTER, DateOperator); operators.set(Operator.DATE_BEFORE, DateOperator); +operators.set(Operator.SEMVER_EQ, SemverOperator); +operators.set(Operator.SEMVER_GT, SemverOperator); +operators.set(Operator.SEMVER_LT, SemverOperator); export class Strategy { public name: string; diff --git a/test/strategy/strategy-test.js b/test/strategy/strategy-test.js index 67b85d1f..1bd6ea92 100644 --- a/test/strategy/strategy-test.js +++ b/test/strategy/strategy-test.js @@ -411,4 +411,92 @@ test('should be disabled when date is not before', (t) => { currentTime: new Date("2022-01-27T14:00:00.000Z"), }; t.false(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + + +test('should be enabled when semver eq', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', + operator: 'SEMVER_EQ', value: '1.2.2' }, + ]; + const context = { + environment: 'dev', + properties: { version: '1.2.2' }, + }; + t.true(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + +test('should be enabled when semver lt', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', + operator: 'SEMVER_LT', value: '1.2.2' }, + ]; + const context = { + environment: 'dev', + properties: { version: '1.2.0' }, + }; + t.true(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + +test('should be enabled when semver gt', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', + operator: 'SEMVER_GT', value: '1.2.2' }, + ]; + const context = { + environment: 'dev', + properties: { version: '1.2.5' }, + }; + t.true(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + +test('should be enabled when semver in range', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', operator: 'SEMVER_GT', value: '1.2.2' }, + { contextName: 'version', operator: 'SEMVER_LT', value: '2.0.0' }, + ]; + + const context = { + environment: 'dev', + properties: { version: '1.2.5' }, + }; + t.true(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + +test('should be disabled when semver out of range', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', operator: 'SEMVER_GT', value: '1.2.2' }, + { contextName: 'version', operator: 'SEMVER_LT', value: '2.0.0' }, + ]; + + const context = { + environment: 'dev', + properties: { version: '1.2.0' }, + }; + t.false(strategy.isEnabledWithConstraints(params, context, constraints)); +}); + +test('should be disabled when semver larger than range', (t) => { + const strategy = new Strategy('test', true); + const params = {}; + const constraints = [ + { contextName: 'version', operator: 'SEMVER_GT', value: '1.2.2' }, + { contextName: 'version', operator: 'SEMVER_LT', value: '2.0.0' }, + ]; + + const context = { + environment: 'dev', + properties: { version: '2.2.1' }, + }; + t.false(strategy.isEnabledWithConstraints(params, context, constraints)); }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e5aec388..62d181e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -523,6 +523,11 @@ resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== +"@types/semver@^7.3.9": + version "7.3.9" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== + "@types/ssri@*": version "7.1.1" resolved "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.1.tgz"