From c8e35c5407f7fa8642d541b49000e77e76cd3f2a Mon Sep 17 00:00:00 2001 From: LaunchDarklyReleaseBot <86431345+LaunchDarklyReleaseBot@users.noreply.github.com> Date: Mon, 6 Jun 2022 09:17:48 -0700 Subject: [PATCH] prepare 6.4.2 release (#250) * revise tests to use new helper package * misc cleanup * use launchdarkly-js-test-helpers 1.0.0 * fix package reference * minor fixes to config validation messages + add comment * diagnostic eventsInQueue counter should be # of events at last flush * rename eventsInQueue to eventsInLastBatch * don't let user fall outside of last bucket in rollout * add unit tests for basic bucketing logic and edge case * avoid redundant property lookups * fix Redis client parameter to match TS declaration (but still support old incorrect parameter) * add event payload ID * remove mistakenly checked-in test code (note, this SDK key was only valid on staging) * add mention of singleton usage * update diagnostic event info for OS name, data store type, Node version * standardize linting * disallow window and document * fix null/undef checks * misc linting fixes * inlineUsersInEvents is not an unknown option * drop node-sha1 dependency * don't omit streamInits.failed when it's false * bump request dependency to get security patch; loosen some exact dependencies * remove request package; improve polling cache logic + add test * bump typescript version to fix build error in Node 6 * update @types/node to fix TypeScript check step * lint * make sure we keep polling regardless of whether we got new data * use launchdarkly-eventsource, make stream retry behavior consistent * stream retry delay option should be in seconds & should be included in diagnostics * minor test fix * fix: Throw an error on malformed user-supplied logger * don't call unref() on Redis client; ensure that database integration tests close the store * update Redis driver to major version 3 * add test case * allow redisOpts parameter to be omitted * add logger adapter shim + tests * minor cleanup and comments for ch74741 fix (logger wrapper) * fix proxy tunnel configuration and make sure it's used in streaming * change some string concatenation expressions to use interpolation * feat: upgrade winston (#189) * fix merge * remove support for indirect/patch and indirect/put (#182) * reuse same Promise and same event listeners for all waitForInitialization calls * better docs for waitForInitialization + misc doc cleanup (#184) * update js-eventsource to 1.3.1 for stream parsing bugfix (#185) * fix broken logger format (#186) * retroactively update changelog for bugfix in 5.13.2 release * allow get/getAll Redis queries to be queued if Redis client hasn't yet connected * set stream read timeout * adding the alias functionality (#190) * Removed the guides link * remove monkey-patching of setImmediate * Persist contextKind property during feature and custom event transformations (#194) * add inlineUsersInEvents option in TypeScript * Add support for seed to bucketUser * Add note for incorporating seed into evaluation * Send events when the evaluation is from an experiment * Use seed to evaluate. * Clean up test descriptions * Rename variable to be less confusing * Use ternary to eliminate mutation * Make return signature more consistent * Un-prettier the tests * redis lower bounds bump (#199) * update launchdarkly-js-test-helpers to fix TLS tests (#200) * update js-eventsource to remove vulnerability warning (#201) * add CI jobs for all compatible Node versions * CI fixes * more CI fixes * comment * use default value to simplify config * (6.0 - #1) stop saying we're compatible with Node <12 (#203) * add CI jobs for all compatible Node versions (#202) * (6.0 - #2) remove Redis integration (#204) * allow feature store to be specified as a factory (so it can get our logger) * (6.0 - #3) remove Winston (#205) * remove deprecated things for 6.0 (#206) * update node-cache to 5.x (drops old Node compat) * update semver to 7.x (drops old Node compat) * update uuid to 8.x (Node compat, perf improvements, bugfixes) * update dev dependencies * linter * replace lrucache package with lru-cache (#209) * make yaml dependency optional (#210) * update release metadata to include maintenance branch * remove package-lock.json (#211) * rm prerelease changelog * (big segments #1) add interfaces for big segments (#212) * (big segments #2) add all components for big segments except evaluation (#213) * (big segments #3) implement big segments in flag evaluation (#214) * (big segments #4) add standard test suite for big segment store tests + refactor feature store tests (#215) * move new interfaces to a module instead of a namespace (#216) * fix TS export of CachingStoreWrapper * use Releaser v2 config * fix overly specific test expectation that breaks in Node 17 * Initial work on FlagBuilder (#219) * Add TestData factory(with some dummy methods); Initial work on FlagBuilder * fixed indentation and linter errors; fixed an error in update; fixed incorrect test label * fixed typo in TestData store * converted boolean variation constants to be file variables instead of class variables Co-authored-by: charukiewicz Co-authored-by: belevy * implemented FlagRuleBuilder; added .build() methods to FlagBuilder/FlagRuleBuilder and changed tests to avoid using private interface * converted _targets to be Map instead of object literal; changed variationForBoolean to be a module-scoped function instead class-scoped * Implement stream processor(data source) interface for test data * Add TestData to index.js and write out the types for TestData and friends * added testdata documentation to index.d.ts; fix linter errors; changed flag default behavior to create boolean flag * Fix the interface file: reindented to 2 spaces, corrected definition of functions from properties to functions in interfaces; corrected issues in JSDoc comments * modify tests to fix capitalization and actually test the test datasource works as an LDClient updateProcessor. * Fix linter error on defaulted callback * explicitly enable JSDOM types in TypeScript build to avoid errors when jsdom is referenced for some reason * capitalize Big Segments in docs & logs * documentation comment fixes for TestData * pin TypeScript to 4.4.x * move TestData and FIleDataSource to integrations module * lint * rename types used by TestData for clarity (#229) * use varargs semantics for TestFlagBuilder.variations() and add it to the TS interface (#230) * don't ever use for...in (#232) * don't ever use for...in * add null guard * bump launchdarkly-eventsource dependency for sc-136154 fix * use TestData in our own tests (#231) * use TestData in our own tests * update TS interface * lint * typo * fix allFlagsState behavior regarding experimentation * lint * allow "secondary" to be referenced in clauses * don't throw an exception for non-string in semver comparison * correctly handle "client not ready" condition in allFlagsState * lint * Flags with a version of 0 reported as 'unknown' in summary events. (#239) * implement contract test service, not including big segments (#242) Co-authored-by: Eli Bishop * Implement Application tags for the node SDK. (#241) * update js-eventsource to 1.4.4 for security fix * remove package-lock.json * adjust test expectation about error message to work in recent Node versions * Adds link to Relay Proxy docs * Update index.d.ts Co-authored-by: Eli Bishop * ensure setTimeout task is cleared when polling is stopped * fix some flaky tests using async blocking logic * rm unused * simplify polling implementation using setInterval * use newer js-test-helpers for async tests * add request number to timeout message * Enforce 64 character limit for application tag values. (#263) Co-authored-by: Eli Bishop Co-authored-by: LaunchDarklyCI Co-authored-by: Ben Woskow Co-authored-by: Maxwell Gerber Co-authored-by: Chris West Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Mike Zorn Co-authored-by: Robert J. Neal Co-authored-by: Ben Levy Co-authored-by: charukiewicz Co-authored-by: belevy Co-authored-by: charukiewicz Co-authored-by: LaunchDarklyReleaseBot Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Co-authored-by: Ember Stevens Co-authored-by: Ember Stevens <79482775+ember-stevens@users.noreply.github.com> --- .circleci/config.yml | 11 +++++++---- CONTRIBUTING.md | 6 ++++++ configuration.js | 4 ++++ messages.js | 2 ++ package.json | 2 +- test/LDClient-tls-test.js | 6 +++--- test/configuration-test.js | 7 +++++++ test/event_processor-test.js | 7 +++---- test/polling-test.js | 16 +++++++-------- test/streaming-test.js | 13 ++++++------ test/test_helpers.js | 38 ------------------------------------ 11 files changed, 47 insertions(+), 65 deletions(-) delete mode 100644 test/test_helpers.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 172cc1c..7cc281e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,14 +16,17 @@ workflows: docker-image: cimg/node:current run-lint: true - build-test-linux: - name: Node 16.3 - docker-image: cimg/node:16.3 + name: Node 17.9 + docker-image: cimg/node:17.9 + - build-test-linux: + name: Node 16.14 + docker-image: cimg/node:16.14 - build-test-linux: name: Node 15.14 docker-image: cimg/node:15.14 - build-test-linux: - name: Node 14.17 - docker-image: cimg/node:14.17 + name: Node 14.19 + docker-image: cimg/node:14.19 - build-test-linux: name: Node 13.14 docker-image: cimg/node:13.14 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 295f510..af7f2f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,12 @@ To verify that the TypeScript declarations compile correctly (this involves comp npm run check-typescript ``` +To run the SDK contract test suite (see [`contract-tests/README.md`](./contract-tests/README.md)): + +```bash +npm run contract-tests +``` + ### Auditing package dependencies The `npm audit` tool compares all dependencies and transitive dependencies to a database of package versions with known vulnerabilities. However, the output of this tool includes both runtime and development dependencies. diff --git a/configuration.js b/configuration.js index 8f0bb3c..cb846a5 100644 --- a/configuration.js +++ b/configuration.js @@ -63,6 +63,10 @@ module.exports = (function () { config.logger.warn(messages.invalidTagValue(name)); return undefined; } + if (tagValue.length > 64) { + config.logger.warn(messages.tagValueTooLong(name)); + return undefined; + } return tagValue; } diff --git a/messages.js b/messages.js index 0912a6c..eee8f8c 100644 --- a/messages.js +++ b/messages.js @@ -27,3 +27,5 @@ exports.wrongOptionTypeBoolean = (name, actualType) => `Config option "${name}" should be a boolean, got ${actualType}, converting to boolean`; exports.invalidTagValue = name => `Config option "${name}" must only contain letters, numbers, ., _ or -.`; + +exports.tagValueTooLong = name => `Value of "${name}" was longer than 64 characters and was discarded.`; diff --git a/package.json b/package.json index f2602fc..86a353c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "eslint-plugin-prettier": "^3.4.0", "jest": "^27.0.4", "jest-junit": "^12.2.0", - "launchdarkly-js-test-helpers": "^1.2.1", + "launchdarkly-js-test-helpers": "^2.2.0", "prettier": "^2.3.1", "tmp": "^0.2.1", "typescript": "~4.4.4", diff --git a/test/LDClient-tls-test.js b/test/LDClient-tls-test.js index 9f0b612..4ede726 100644 --- a/test/LDClient-tls-test.js +++ b/test/LDClient-tls-test.js @@ -4,10 +4,10 @@ import { AsyncQueue, TestHttpHandlers, TestHttpServer, + failOnTimeout, withCloseable } from 'launchdarkly-js-test-helpers'; import * as stubs from './stubs'; -import { failIfTimeout } from './test_helpers'; describe('LDClient TLS configuration', () => { const sdkKey = 'secret'; @@ -46,9 +46,9 @@ describe('LDClient TLS configuration', () => { }; await withCloseable(LDClient.init(sdkKey, config), async client => { - const message1 = await failIfTimeout(logCapture.warn.take(), 1000); + const message1 = await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message'); expect(message1).toMatch(/only disable the streaming API/); // irrelevant message due to our use of polling mode - const message2 = await failIfTimeout(logCapture.warn.take(), 1000); + const message2 = await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message'); expect(message2).toMatch(/self.signed/); }); }); diff --git a/test/configuration-test.js b/test/configuration-test.js index bd96ce9..6334e5f 100644 --- a/test/configuration-test.js +++ b/test/configuration-test.js @@ -196,6 +196,13 @@ describe('configuration', function() { expect(configIn.logger.warn).toHaveBeenCalledTimes(1); }); + it('logs a warning when a tag value is too long', async () => { + const configIn = emptyConfigWithMockLogger(); + configIn.application = { id: 'a'.repeat(65), version: 'b'.repeat(64) }; + expect(configuration.validate(configIn).application.id).toBeUndefined(); + expect(configIn.logger.warn).toHaveBeenCalledTimes(1); + }); + it('handles a valid application version', () => { const configIn = emptyConfigWithMockLogger(); configIn.application = {version: 'test-version'}; diff --git a/test/event_processor-test.js b/test/event_processor-test.js index 612a09b..3092f45 100644 --- a/test/event_processor-test.js +++ b/test/event_processor-test.js @@ -1,7 +1,6 @@ const { DiagnosticsManager, DiagnosticId } = require('../diagnostic_events'); const EventProcessor = require('../event_processor'); -const { TestHttpHandlers, TestHttpServer, withCloseable } = require('launchdarkly-js-test-helpers'); -const { failIfTimeout } = require('./test_helpers'); +const { failOnTimeout, TestHttpHandlers, TestHttpServer, withCloseable } = require('launchdarkly-js-test-helpers'); describe('EventProcessor', () => { @@ -657,8 +656,8 @@ describe('EventProcessor', () => { ep.sendEvent({ kind: 'identify', creationDate: 1000, user: user }); // unfortunately we must wait for both the flush interval and the 1-second retry interval - await failIfTimeout(s.nextRequest(), 500); - await failIfTimeout(s.nextRequest(), 1500); + await failOnTimeout(s.nextRequest(), 500, 'timed out waiting for event payload'); + await failOnTimeout(s.nextRequest(), 1500, 'timed out waiting for event payload'); }); })); diff --git a/test/polling-test.js b/test/polling-test.js index ae4162a..171b7ae 100644 --- a/test/polling-test.js +++ b/test/polling-test.js @@ -1,9 +1,8 @@ const InMemoryFeatureStore = require('../feature_store'); const PollingProcessor = require('../polling'); const dataKind = require('../versioned_data_kind'); -const { AsyncQueue, promisify, promisifySingle } = require('launchdarkly-js-test-helpers'); +const { AsyncQueue, failOnResolve, failOnTimeout, promisify, promisifySingle } = require('launchdarkly-js-test-helpers'); const stubs = require('./stubs'); -const { failIfTimeout } = require('./test_helpers'); describe('PollingProcessor', () => { const longInterval = 100000; @@ -81,7 +80,7 @@ describe('PollingProcessor', () => { processor.start(() => {}); const startTime = new Date().getTime(); for (let i = 0; i < 4; i++) { - await failIfTimeout(calls.take(), 500); + await failOnTimeout(calls.take(), 500, 'timed out waiting for poll request #' + (i + 1)); } expect(new Date().getTime() - startTime).toBeLessThanOrEqual(500); }); @@ -107,9 +106,9 @@ describe('PollingProcessor', () => { let errReceived; processor.start(e => { errReceived = e; }); - await failIfTimeout(calls.take(), 500); - await failIfTimeout(calls.take(), 500); - await failIfTimeout(calls.take(), 500); + for (let i = 0; i < 3; i++) { + await failOnTimeout(calls.take(), 500, 'timed out waiting for poll request #' + (i + 1)); + } expect(config.logger.error).not.toHaveBeenCalled(); expect(errReceived).toBeUndefined(); @@ -143,10 +142,11 @@ describe('PollingProcessor', () => { const result = new AsyncQueue(); processor.start(e => result.add(e)); - const errReceived = await failIfTimeout(result.take(), 1000); + const errReceived = await failOnTimeout(result.take(), 1000, 'timed out waiting for initialization to complete'); expect(errReceived.message).toMatch(new RegExp('error ' + status + '.*giving up permanently')); - expect(calls.length()).toEqual(1); + await failOnTimeout(calls.take(), 10, 'expected initial poll request but did not see one'); + await failOnResolve(calls.take(), 100, 'received unexpected second poll request'); expect(config.logger.error).toHaveBeenCalledTimes(1); } diff --git a/test/streaming-test.js b/test/streaming-test.js index e2ac9c6..7d71700 100644 --- a/test/streaming-test.js +++ b/test/streaming-test.js @@ -2,10 +2,9 @@ const { DiagnosticId, DiagnosticsManager } = require('../diagnostic_events'); const InMemoryFeatureStore = require('../feature_store'); const StreamProcessor = require('../streaming'); import * as httpUtils from '../utils/httpUtils'; -import { failIfResolves, failIfTimeout } from './test_helpers'; const dataKind = require('../versioned_data_kind'); -const { promisifySingle } = require('launchdarkly-js-test-helpers'); +const { failOnResolve, failOnTimeout, promisifySingle } = require('launchdarkly-js-test-helpers'); const stubs = require('./stubs'); describe('StreamProcessor', () => { @@ -258,10 +257,10 @@ describe('StreamProcessor', () => { es.instance.simulateError(err); - await failIfTimeout(logCapture.warn.take(), 1000); + await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message'); - await failIfResolves(waitForStart, 50); - await failIfResolves(logCapture.error.take(), 50); + await failOnResolve(waitForStart, 50, 'initialization completed unexpectedly'); + await failOnResolve(logCapture.error.take(), 50, 'got unexpected log error'); expect(es.closed).not.toBeTruthy(); @@ -297,11 +296,11 @@ describe('StreamProcessor', () => { const waitForStart = promisifySingle(sp.start)(); es.instance.simulateError(err); - const errReceived = await failIfTimeout(waitForStart, 1000); + const errReceived = await failOnTimeout(waitForStart, 1000, 'timed out waiting for error result'); expect(errReceived).toEqual(err); - await failIfTimeout(logCapture.error.take(), 50); + await failOnTimeout(logCapture.error.take(), 50, 'timed out waiting for log error'); expect(es.closed).toBe(true); diff --git a/test/test_helpers.js b/test/test_helpers.js deleted file mode 100644 index cf25225..0000000 --- a/test/test_helpers.js +++ /dev/null @@ -1,38 +0,0 @@ - -function failIfResolves(promise, timeout, waitingForWhat) { - let timer; - return Promise.race([ - new Promise( - (resolve, reject) => { - promise.then(() => { - timer && clearTimeout(timer); - reject('received unexpected ' + (waitingForWhat || 'value')); - }) - } - ), - new Promise( - (resolve) => { - timer = setTimeout(resolve, timeout); - } - ), - ]); -} - -function failIfTimeout(promise, timeout, waitingForWhat) { - let timer; - return Promise.race([ - promise.finally(timer && clearTimeout(timer)), - new Promise( - (resolve, reject) => { - timer = setTimeout(() => { - reject(waitingForWhat ? 'timed out waiting for ' + waitingForWhat : 'timed out'); - }, timeout); - } - ), - ]); -} - -module.exports = { - failIfResolves, - failIfTimeout, -};