-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tests for the sdk directory (#9)
- Loading branch information
Showing
9 changed files
with
507 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { describe, expect, test, vi } from "vitest"; | ||
import { EventProcessor } from "./EventProcessor"; | ||
import { convexTest } from "convex-test"; | ||
import schema from "../component/schema"; | ||
import { modules } from "../component/setup.test"; | ||
import { api } from "../component/_generated/api"; | ||
import { sendEvents } from "../sdk/EventProcessor"; | ||
|
||
describe("EventProcessor", () => { | ||
vi.mock("../sdk/EventProcessor", async (importOriginal) => { | ||
const original = | ||
await importOriginal<typeof import("../sdk/EventProcessor")>(); | ||
return { | ||
...original, | ||
sendEvents: vi.fn(), | ||
}; | ||
}); | ||
|
||
test("sendEvents should send events correctly", async () => { | ||
vi.useFakeTimers(); | ||
const events = [{ payload: "event1" }, { payload: "event2" }]; | ||
const sdkKey = "test-sdk-key"; | ||
|
||
const t = convexTest(schema, modules); | ||
|
||
await t.run(async (ctx) => { | ||
// @ts-expect-error It's ok | ||
const eventProcessor = new EventProcessor(api.events, ctx, sdkKey); | ||
|
||
await eventProcessor.sendEvent(events[0]); | ||
}); | ||
|
||
await t.finishAllScheduledFunctions(vi.runAllTimers); | ||
|
||
expect(sendEvents).toHaveBeenCalledOnce(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { describe, expect, test, vi } from "vitest"; | ||
import { FeatureStore } from "./FeatureStore"; | ||
import { convexTest } from "convex-test"; | ||
import schema from "../component/schema"; | ||
import { modules } from "../component/setup.test"; | ||
import { api } from "../component/_generated/api"; | ||
import { write } from "../component/store"; | ||
import * as store from "../component/store"; | ||
|
||
describe("FeatureStore", () => { | ||
describe("get", () => { | ||
test("invalid kind should throw an error", async () => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const invalidKind = "invalidKind"; | ||
|
||
await expect( | ||
featureStore.get({ namespace: invalidKind }, "someKey", vi.fn()) | ||
).rejects.toThrow(new Error(`Unsupported DataKind: ${invalidKind}`)); | ||
}); | ||
}); | ||
|
||
test.each(["features", "segments"])( | ||
"get should return null when not initialized for kind %s", | ||
async (namespace) => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const k = { | ||
namespace: namespace, | ||
}; | ||
const key = "nonExistingKey"; | ||
const consoleSpy = vi | ||
.spyOn(console, "error") | ||
.mockImplementation(() => {}); | ||
|
||
await featureStore.get(k, key, (res) => { | ||
expect(res).toBeNull(); | ||
}); | ||
expect(consoleSpy).toHaveBeenCalledWith( | ||
"The LaunchDarkly data store has not been initialized. Is your integration configuration correct?" | ||
); | ||
}); | ||
} | ||
); | ||
|
||
test.each(["features", "segments"])( | ||
"get should return cached value for kind %s", | ||
async (kind) => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
const config = { key: "existingKey", version: 1 }; | ||
|
||
await write(ctx, { | ||
payload: JSON.stringify({ | ||
flags: { | ||
[config.key]: config, | ||
}, | ||
segments: { | ||
[config.key]: config, | ||
}, | ||
}), | ||
}); | ||
|
||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const k = { namespace: kind }; | ||
const key = config.key; | ||
|
||
const spy = spyOnQuery(store, "get"); | ||
|
||
await featureStore.get(k, key, (res) => { | ||
expect(res).toEqual(config); | ||
}); | ||
|
||
expect(spy).toHaveBeenCalledOnce(); | ||
|
||
await featureStore.get(k, key, (res) => { | ||
expect(res).toEqual(config); | ||
}); | ||
|
||
expect(spy).toHaveBeenCalledOnce(); // should not call get again | ||
}); | ||
} | ||
); | ||
}); | ||
|
||
describe("all", () => { | ||
test("invalid kind should throw an error", async () => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const invalidKind = "invalidKind"; | ||
|
||
await expect( | ||
featureStore.all({ namespace: invalidKind }, vi.fn()) | ||
).rejects.toThrow(new Error(`Unsupported DataKind: ${invalidKind}`)); | ||
}); | ||
}); | ||
|
||
test.each(["features", "segments"])( | ||
"all should return empty object when not initialized for kind %s", | ||
async (kind) => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const k = { namespace: kind }; | ||
|
||
await featureStore.all(k, (res) => { | ||
expect(res).toEqual({}); | ||
}); | ||
}); | ||
} | ||
); | ||
|
||
test.each(["features", "segments"])( | ||
"all should return cached values for kind %s", | ||
async (kind) => { | ||
const t = convexTest(schema, modules); | ||
await t.run(async (ctx) => { | ||
const config = { key: "existingKey", version: 1 }; | ||
|
||
await write(ctx, { | ||
payload: JSON.stringify({ | ||
flags: { | ||
[config.key]: config, | ||
}, | ||
segments: { | ||
[config.key]: config, | ||
}, | ||
}), | ||
}); | ||
|
||
// @ts-expect-error It's ok | ||
const featureStore = new FeatureStore(ctx, api.store, console); | ||
const k = { namespace: kind }; | ||
|
||
const getAllSpy = spyOnQuery(store, "getAll"); | ||
|
||
await featureStore.all(k, (res) => { | ||
expect(res).toEqual({ [config.key]: config }); | ||
}); | ||
|
||
expect(getAllSpy).toHaveBeenCalledOnce(); | ||
|
||
await featureStore.all(k, (res) => { | ||
expect(res).toEqual({ [config.key]: config }); | ||
}); | ||
|
||
expect(getAllSpy).toHaveBeenCalledOnce(); // should not call getAll again | ||
|
||
const getSpy = spyOnQuery(store, "get"); | ||
|
||
await featureStore.get(k, config.key, (res) => { | ||
expect(res).toEqual(config); | ||
}); | ||
|
||
expect(getSpy).toHaveBeenCalledTimes(0); // should not call get because getAll was already called | ||
}); | ||
} | ||
); | ||
}); | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function spyOnQuery(api: any, method: string) { | ||
const spy = vi.spyOn(api, method); | ||
// @ts-expect-error It's a query ;) | ||
spy.isQuery = true; | ||
// @ts-expect-error Ignore validators | ||
spy.exportArgs = () => "{}"; | ||
return spy; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { describe, expect, test, vi } from "vitest"; | ||
import { LaunchDarkly } from "./LaunchDarkly"; | ||
import { api } from "../component/_generated/api"; | ||
import { EventProcessor } from "./EventProcessor"; | ||
|
||
describe("LaunchDarkly", () => { | ||
// The LaunchDarkly internals sometimes call functions that use setTimeout and setInterval. | ||
// Ensure that our configuration is set up such that they are not called. | ||
test("initializing class does not crash and setTimeout and setInterval are not called", async () => { | ||
const setTimeoutSpy = vi.spyOn(global, "setTimeout"); | ||
const setIntervalSpy = vi.spyOn(global, "setInterval"); | ||
new LaunchDarkly( | ||
// @ts-expect-error It's ok | ||
api, | ||
{}, | ||
{ | ||
LAUNCHDARKLY_SDK_KEY: "test-key", | ||
} | ||
); | ||
|
||
expect(setTimeoutSpy).not.toHaveBeenCalled(); | ||
expect(setIntervalSpy).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test("should throw an error if LAUNCHDARKLY_SDK_KEY is not provided", async () => { | ||
await expect( | ||
// @ts-expect-error It's ok | ||
() => new LaunchDarkly(api, {}, {}) | ||
).toThrow(new Error("LAUNCHDARKLY_SDK_KEY is required")); | ||
}); | ||
|
||
test("should not throw an error if the env var is set", () => { | ||
vi.stubEnv("LAUNCHDARKLY_SDK_KEY", "test-key"); | ||
|
||
expect(() => { | ||
// @ts-expect-error It's ok | ||
new LaunchDarkly(api, {}, {}); | ||
}).not.toThrow(); | ||
|
||
vi.unstubAllEnvs(); | ||
}); | ||
|
||
test("should not configure the EventProcessor when in a query", () => { | ||
const ld = new LaunchDarkly( | ||
// @ts-expect-error It's ok | ||
api, | ||
{}, | ||
{ | ||
LAUNCHDARKLY_SDK_KEY: "test-key", | ||
} | ||
); | ||
|
||
// @ts-expect-error We are testing internal state | ||
expect(ld.eventProcessor).not.toBeInstanceOf(EventProcessor); | ||
}); | ||
|
||
test("should configure the EventProcessor when in a mutation", () => { | ||
const ld = new LaunchDarkly( | ||
// @ts-expect-error It's ok | ||
api, | ||
{ runMutation: () => {} }, | ||
{ | ||
LAUNCHDARKLY_SDK_KEY: "test-key", | ||
} | ||
); | ||
|
||
// @ts-expect-error We are testing internal state | ||
expect(ld.eventProcessor).toBeInstanceOf(EventProcessor); | ||
}); | ||
|
||
test("should not configure the EventProcessor when sendEvents is false", () => { | ||
const ld = new LaunchDarkly( | ||
// @ts-expect-error It's ok | ||
api, | ||
{ runMutation: () => {} }, | ||
{ | ||
LAUNCHDARKLY_SDK_KEY: "test-key", | ||
sendEvents: false, | ||
} | ||
); | ||
|
||
// @ts-expect-error We are testing internal state | ||
expect(ld.eventProcessor).not.toBeInstanceOf(EventProcessor); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.