2022-10-20 14:02:22 -07:00
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { assert } from 'chai';
|
2025-08-22 11:20:57 -04:00
|
|
|
import * as sinon from 'sinon';
|
2022-10-20 14:02:22 -07:00
|
|
|
|
2025-09-19 13:05:51 -07:00
|
|
|
import lodash from 'lodash';
|
2025-09-16 17:39:03 -07:00
|
|
|
import { normalizeAci } from '../util/normalizeAci.js';
|
|
|
|
import type { ConfigKeyType, ConfigListenerType } from '../RemoteConfig.js';
|
2022-10-20 14:02:22 -07:00
|
|
|
import {
|
|
|
|
getCountryCodeValue,
|
|
|
|
getBucketValue,
|
|
|
|
innerIsBucketValueEnabled,
|
2025-08-22 11:20:57 -04:00
|
|
|
onChange,
|
|
|
|
getValue,
|
|
|
|
isEnabled,
|
2025-09-16 17:39:03 -07:00
|
|
|
} from '../RemoteConfig.js';
|
|
|
|
import { updateRemoteConfig } from '../test-helpers/RemoteConfigStub.js';
|
2022-10-20 14:02:22 -07:00
|
|
|
|
2025-09-19 13:05:51 -07:00
|
|
|
const { omit } = lodash;
|
|
|
|
|
2022-10-20 14:02:22 -07:00
|
|
|
describe('RemoteConfig', () => {
|
2023-12-08 00:59:54 +01:00
|
|
|
const aci = normalizeAci('95b9729c-51ea-4ddb-b516-652befe78062', 'test');
|
2022-10-20 14:02:22 -07:00
|
|
|
|
|
|
|
describe('#innerIsBucketValueEnabled', () => {
|
2024-02-07 16:34:31 -08:00
|
|
|
// Note: bucketValue is 376321 for 'desktop.internalUser' key
|
2022-10-20 14:02:22 -07:00
|
|
|
|
|
|
|
it('returns true for 100% wildcard', () => {
|
|
|
|
assert.strictEqual(
|
|
|
|
innerIsBucketValueEnabled(
|
2024-02-07 16:34:31 -08:00
|
|
|
'desktop.internalUser',
|
2022-10-20 14:02:22 -07:00
|
|
|
'*:1000000',
|
|
|
|
'+12125550000',
|
2023-08-16 22:54:39 +02:00
|
|
|
aci
|
2022-10-20 14:02:22 -07:00
|
|
|
),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-08 00:59:54 +01:00
|
|
|
it('returns true for 70% on country code 1', () => {
|
2022-10-20 14:02:22 -07:00
|
|
|
assert.strictEqual(
|
|
|
|
innerIsBucketValueEnabled(
|
2024-02-07 16:34:31 -08:00
|
|
|
'desktop.internalUser',
|
2023-12-08 00:59:54 +01:00
|
|
|
'1:700000',
|
2022-10-20 14:02:22 -07:00
|
|
|
'+12125550000',
|
2023-08-16 22:54:39 +02:00
|
|
|
aci
|
2022-10-20 14:02:22 -07:00
|
|
|
),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2024-02-07 16:34:31 -08:00
|
|
|
it('returns false for 30% on country code 1', () => {
|
2022-10-20 14:02:22 -07:00
|
|
|
assert.strictEqual(
|
|
|
|
innerIsBucketValueEnabled(
|
2024-02-07 16:34:31 -08:00
|
|
|
'desktop.internalUser',
|
|
|
|
'1:300000',
|
2022-10-20 14:02:22 -07:00
|
|
|
'+12125550000',
|
2023-08-16 22:54:39 +02:00
|
|
|
aci
|
2022-10-20 14:02:22 -07:00
|
|
|
),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#getCountryCodeValue', () => {
|
|
|
|
it('returns undefined for empty value', () => {
|
|
|
|
assert.strictEqual(getCountryCodeValue(1, '', 'flagName'), undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws for malformed flag', () => {
|
|
|
|
assert.throws(
|
|
|
|
() => getCountryCodeValue(1, 'hi:::', 'flagName'),
|
|
|
|
"invalid number ''"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws for non-integer value', () => {
|
|
|
|
assert.throws(
|
|
|
|
() => getCountryCodeValue(1, '1:cd', 'flagName'),
|
|
|
|
"invalid number 'cd'"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns wildcard value if no other codes', () => {
|
|
|
|
assert.strictEqual(getCountryCodeValue(1, '*:56,2:74', 'flagName'), 56);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns value for specific codes, instead of wildcard', () => {
|
|
|
|
assert.strictEqual(getCountryCodeValue(1, '*:56,1:74', 'flagName'), 74);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns undefined if no wildcard or specific value', () => {
|
|
|
|
assert.strictEqual(
|
|
|
|
getCountryCodeValue(1, '2:56,3:74', 'flagName'),
|
|
|
|
undefined
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#getBucketValue', () => {
|
|
|
|
it('returns undefined for empty value', () => {
|
|
|
|
const flagName = 'research.megaphone.1';
|
|
|
|
|
2023-12-08 00:59:54 +01:00
|
|
|
assert.strictEqual(getBucketValue(aci, flagName), 222732);
|
2022-10-20 14:02:22 -07:00
|
|
|
});
|
|
|
|
});
|
2025-08-22 11:20:57 -04:00
|
|
|
|
|
|
|
describe('#getValue', () => {
|
|
|
|
it('returns value if enabled', async () => {
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
|
|
|
|
assert.equal(getValue('desktop.internalUser'), undefined);
|
|
|
|
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes' },
|
|
|
|
]);
|
|
|
|
assert.equal(getValue('desktop.internalUser'), 'yes');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not return disabled value', async () => {
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
assert.equal(getValue('desktop.internalUser'), undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#isEnabled', () => {
|
|
|
|
it('is false for missing flag', async () => {
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
assert.equal(isEnabled('desktop.internalUser'), false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('is false for disabled flag', async () => {
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
assert.equal(isEnabled('desktop.internalUser'), false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('is true for enabled flag', async () => {
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes' },
|
|
|
|
]);
|
|
|
|
assert.equal(isEnabled('desktop.internalUser'), true);
|
|
|
|
});
|
|
|
|
|
2025-09-02 16:42:04 -07:00
|
|
|
it('is true for true string flag', async () => {
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'true' },
|
|
|
|
]);
|
|
|
|
assert.equal(isEnabled('desktop.internalUser'), true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('is false for false string flag', async () => {
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'false' },
|
|
|
|
]);
|
|
|
|
assert.equal(isEnabled('desktop.internalUser'), false);
|
|
|
|
});
|
|
|
|
|
2025-08-22 11:20:57 -04:00
|
|
|
it('reflects the value of an unknown flag in the config', async () => {
|
|
|
|
assert.equal(
|
|
|
|
isEnabled('desktop.unknownFlagName' as ConfigKeyType),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.unknownFlagName', value: 'unknownFlagValue' },
|
|
|
|
]);
|
|
|
|
assert.equal(isEnabled('desktop.unknownFlagName' as ConfigKeyType), true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#onChange', () => {
|
|
|
|
it('triggers listener on known flag change', async () => {
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
|
const listener = sinon.spy<ConfigListenerType>(() => {});
|
|
|
|
onChange('desktop.internalUser', listener);
|
|
|
|
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes' },
|
|
|
|
]);
|
|
|
|
await updateRemoteConfig([]);
|
|
|
|
await updateRemoteConfig([
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes' },
|
|
|
|
]);
|
|
|
|
|
|
|
|
const calls = listener
|
|
|
|
.getCalls()
|
|
|
|
.map(call => omit(call.firstArg, 'enabledAt'));
|
|
|
|
assert.deepEqual(calls, [
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes', enabled: true },
|
|
|
|
{ name: 'desktop.internalUser', enabled: false },
|
|
|
|
{ name: 'desktop.internalUser', value: 'yes', enabled: true },
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
2022-10-20 14:02:22 -07:00
|
|
|
});
|