Skip to content

Commit

Permalink
DVC-6570 run all bucketing tests against variableForUser() and variab…
Browse files Browse the repository at this point in the history
…leForUserPB() (#434)

* run bucketing tests against variableForUser() and variableForUserPB()

* cleanup
  • Loading branch information
jonathannorris authored Mar 13, 2023
1 parent 3f0d889 commit 3ff5363
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ const { config, barrenConfig } = testData

import moment from 'moment'
import * as uuid from 'uuid'
import { BucketedUserConfig, SDKVariable } from '../../assembly/types'
import {
BucketedUserConfig,
SDKVariable,
} from '@devcycle/types'
import { cleanupSDK, initSDK } from '../setPlatformData'
import { variableForUserPB, VariableForUserArgs } from '../protobufVariableHelper'

type BoundedHash = { rolloutHash: number, bucketingHash: number }

Expand Down Expand Up @@ -54,10 +58,25 @@ const generateBucketedConfig = (user: unknown): BucketedUserConfig => {
return JSON.parse(bucketedConfig) as BucketedUserConfig
}

const variableForUser = (
{ user, variableKey, variableType }:
{ user: unknown, variableKey: string, variableType: VariableType }
): SDKVariable | null => {
const expectVariableForUser = (
args: { user: any, variableKey: string, variableType: VariableType },
expectedValue: unknown
) => {
const variable = variableForUser({ ...args, sdkKey })
const pbVariable = variableForUserPB({ ...args, sdkKey })

if (expectedValue === null) {
expect(variable).toBeNull()
expect(pbVariable).toBeNull()
} else {
expect(variable).not.toBeNull()
expect(variable).toEqual(expectedValue)
expect(pbVariable).not.toBeNull()
expect(pbVariable).toEqual(expectedValue)
}
}

const variableForUser = ({ user, variableKey, variableType }: VariableForUserArgs): SDKVariable | null => {
const variableJSON = variableForUser_AS(
sdkKey, JSON.stringify(user), variableKey, variableType, true
)
Expand Down Expand Up @@ -222,8 +241,10 @@ describe('Config Parsing and Generating', () => {
const c = generateBucketedConfig(user)
expect(c).toEqual(expected)

expect(variableForUser({ user, variableKey: 'swagTest', variableType: VariableType.String }))
.toEqual(expected.variables.swagTest)
expectVariableForUser(
{ user, variableKey: 'swagTest', variableType: VariableType.String },
expected.variables.swagTest
)
})

it('puts the user in the target for the first audience they match', () => {
Expand Down Expand Up @@ -372,16 +393,26 @@ describe('Config Parsing and Generating', () => {
const c = generateBucketedConfig(user)
expect(c).toEqual(expected)

expect(variableForUser({ user, variableKey: 'audience-match', variableType: VariableType.String }))
.toEqual(expected.variables['audience-match'])
expect(variableForUser({ user, variableKey: 'feature2.cool', variableType: VariableType.String }))
.toEqual(expected.variables['feature2.cool'])
expect(variableForUser({ user, variableKey: 'feature2.hello', variableType: VariableType.String }))
.toEqual(expected.variables['feature2.hello'])
expect(variableForUser({ user, variableKey: 'swagTest', variableType: VariableType.String }))
.toEqual(expected.variables['swagTest'])
expect(variableForUser({ user, variableKey: 'test', variableType: VariableType.String }))
.toEqual(expected.variables['test'])
expectVariableForUser(
{ user, variableKey: 'audience-match', variableType: VariableType.String },
expected.variables['audience-match']
)
expectVariableForUser(
{ user, variableKey: 'feature2.cool', variableType: VariableType.String },
expected.variables['feature2.cool']
)
expectVariableForUser(
{ user, variableKey: 'feature2.hello', variableType: VariableType.String },
expected.variables['feature2.hello']
)
expectVariableForUser(
{ user, variableKey: 'swagTest', variableType: VariableType.String },
expected.variables['swagTest']
)
expectVariableForUser(
{ user, variableKey: 'test', variableType: VariableType.String },
expected.variables['test']
)
})

it('holds user back if not in rollout', () => {
Expand Down Expand Up @@ -439,11 +470,14 @@ describe('Config Parsing and Generating', () => {
}
}
initSDK(sdkKey, config)

const c = generateBucketedConfig(user)
expect(c).toEqual(expected)

expect(variableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }))
.toEqual(expected.variables['feature2Var'])
expectVariableForUser(
{ user, variableKey: 'feature2Var', variableType: VariableType.String },
expected.variables['feature2Var']
)
})

it('puts user through if in rollout', () => {
Expand Down Expand Up @@ -551,10 +585,14 @@ describe('Config Parsing and Generating', () => {
const c = generateBucketedConfig(user)
expect(c).toEqual(expected)

expect(variableForUser({ user, variableKey: 'swagTest', variableType: VariableType.String }))
.toEqual(expected.variables['swagTest'])
expect(variableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }))
.toEqual(expected.variables['feature2Var'])
expectVariableForUser(
{ user, variableKey: 'swagTest', variableType: VariableType.String },
expected.variables['swagTest']
)
expectVariableForUser(
{ user, variableKey: 'feature2Var', variableType: VariableType.String },
expected.variables['feature2Var']
)
})

it('errors when feature missing distribution', () => {
Expand All @@ -567,8 +605,7 @@ describe('Config Parsing and Generating', () => {
expect(() => generateBucketedConfig(user))
.toThrow('Failed to decide target variation: 61536f3bc838a705c105eb62')

expect(variableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }))
.toBeNull()
expectVariableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }, null)
})

it('errors when config missing variations', () => {
Expand All @@ -591,8 +628,7 @@ describe('Config Parsing and Generating', () => {
expect(() => generateBucketedConfig(user))
.toThrow('Config missing variation: 615382338424cb11646d7667')

expect(variableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }))
.toBeNull()
expectVariableForUser({ user, variableKey: 'feature2Var', variableType: VariableType.String }, null)
})

it('errors when config missing variables', () => {
Expand All @@ -605,8 +641,7 @@ describe('Config Parsing and Generating', () => {
expect(() => generateBucketedConfig(user))
.toThrow('Config missing variable: 61538237b0a70b58ae6af71g')

expect(variableForUser({ user, variableKey: 'feature2.cool', variableType: VariableType.String }))
.toBeNull()
expectVariableForUser({ user, variableKey: 'feature2.cool', variableType: VariableType.String }, null)
})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import path from 'path'
import protobuf from 'protobufjs'
import { SDKVariable, VariableType as VariableTypeStr } from '@devcycle/types'
import {
variableForUser_PB, VariableType
} from './bucketingImportHelper'

// TODO replace all this PB importing + types once we import the pb file directly into static JS Classes
const protoFile = '../protobuf/variableForUserParams.proto'
const filePath = path.resolve(__dirname, protoFile)
const root = protobuf.loadSync(filePath)

const VariableForUserParams_PB = root.lookupType('VariableForUserParams_PB')
if (!VariableForUserParams_PB) throw new Error('VariableForUserParams_PB not found')

const SDKVariable_PB = root.lookupType('SDKVariable_PB')
if (!SDKVariable_PB) throw new Error('SDKVariable_PB not found')

type SDKVariable_PB_Type = {
_id: string
type: number
key: string
boolValue: boolean
doubleValue: number
stringValue: string
}

const pbSDKVariableToJS = (pbSDKVariable: SDKVariable_PB_Type): SDKVariable => {
if (pbSDKVariable.type === 0) {
return {
_id: pbSDKVariable._id,
key: pbSDKVariable.key,
value: pbSDKVariable.boolValue,
type: VariableTypeStr.boolean
}
} else if (pbSDKVariable.type === 1) {
return {
_id: pbSDKVariable._id,
key: pbSDKVariable.key,
value: pbSDKVariable.doubleValue,
type: VariableTypeStr.number
}
} else if (pbSDKVariable.type === 2) {
return {
_id: pbSDKVariable._id,
key: pbSDKVariable.key,
value: pbSDKVariable.stringValue,
type: VariableTypeStr.string
}
} else if (pbSDKVariable.type === 3) {
return {
_id: pbSDKVariable._id,
key: pbSDKVariable.key,
value: JSON.parse(pbSDKVariable.stringValue),
type: VariableTypeStr.json
}
}
throw new Error(`Unknown variable type: ${pbSDKVariable.type}`)
}

enum CustomDataTypePB {
Bool,
Num,
Str,
Null
}

type CustomDataValuePB = {
type: CustomDataTypePB,
boolValue?: boolean,
doubleValue?: number,
stringValue?: string
}

const customDataToPB = (customData: any): Record<string, CustomDataValuePB> | undefined => {
if (!customData) return undefined

const customDataPB: Record<string, CustomDataValuePB> = {}
for (const [key, value] of Object.entries(customData)) {
if (typeof value === 'boolean') {
customDataPB[key] = { type: CustomDataTypePB.Bool, boolValue: value }
} else if (typeof value === 'number') {
customDataPB[key] = { type: CustomDataTypePB.Num, doubleValue: value }
} else if (typeof value === 'string') {
customDataPB[key] = { type: CustomDataTypePB.Str, stringValue: value }
} else if (value === null) {
customDataPB[key] = { type: CustomDataTypePB.Null }
} else {
throw new Error(`Unknown custom data type: ${typeof value}`)
}
}
return customDataPB
}

export type VariableForUserArgs = { sdkKey: string, user: any, variableKey: string, variableType: VariableType }

export const variableForUserPB = (
{ sdkKey, user, variableKey, variableType }: VariableForUserArgs
): SDKVariable | null => {
const customData = customDataToPB(user.customData)
const privateCustomData = customDataToPB(user.privateCustomData)
const params = {
sdkKey,
variableKey,
variableType,
user: {
userId: user.user_id,
email: user.email ? { value: user.email, isNull: false } : undefined,
name: user.name ? { value: user.name, isNull: false } : undefined,
language: user.language ? { value: user.language, isNull: false } : undefined,
country: user.country ? { value: user.country, isNull: false } : undefined,
appBuild: user.appBuild ? { value: user.appBuild, isNull: false } : undefined,
appVersion: user.appVersion ? { value: user.appVersion, isNull: false } : undefined,
deviceModel: user.deviceModel ? { value: user.deviceModel, isNull: false } : undefined,
customData: customData ? { value: customData, isNull: false } : undefined,
privateCustomData: privateCustomData ? { value: privateCustomData, isNull: false } : undefined
},
shouldTrackEvent: true
}
const err = VariableForUserParams_PB.verify(params)
if (err) throw new Error(err)

const pbMsg = VariableForUserParams_PB.create(params)
const buffer = VariableForUserParams_PB.encode(pbMsg).finish()
const resultBuffer = variableForUser_PB(buffer)
if (!resultBuffer) return null
const pbSDKVariable = SDKVariable_PB.decode(resultBuffer!) as unknown as SDKVariable_PB_Type
return pbSDKVariableToJS(pbSDKVariable)
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('protobuf variable tests', () => {
})

const varForUserParams = {
sdkKey: sdkKey,
sdkKey,
variableKey: 'swagTest',
variableType: 2,
shouldTrackEvent: true,
Expand Down
1 change: 0 additions & 1 deletion lib/shared/bucketing-assembly-script/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export function variableForUser_PB(protobuf: Uint8Array): Uint8Array | null {
variableTypeFromPB(params.variableType),
params.shouldTrackEvent
)

return variable ? variable.toProtobuf() : null
}

Expand Down

4 comments on commit 3ff5363

@vercel
Copy link

@vercel vercel bot commented on 3ff5363 Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 3ff5363 Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 3ff5363 Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 3ff5363 Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

js-sdks-next-js – ./

dvc-nextjs.vercel.app
js-sdks-next-js-devcyclehq.vercel.app
js-sdks-next-js-git-main-devcyclehq.vercel.app

Please sign in to comment.