@june-so/analytics-node
Version:
The hassle-free way to integrate analytics into any Node.js application
390 lines (324 loc) • 9.28 kB
text/typescript
import { FetchHTTPClient } from "@segment/analytics-node";
import { Analytics } from "../client";
const extractData = (fetchAction: jest.Mock, i = 0) => {
const [url, request] = fetchAction.mock.calls[i];
const { body } = request as any;
const { batch } = JSON.parse(body);
return { url, batch };
};
describe("Client", () => {
it("should be able to create a new instance", () => {
// given
const analytics = new Analytics("writeKey");
// then
expect(analytics).toBeDefined();
});
it("should not be able to create a new instance without writeKey", () => {
// given
// then
expect(() => new Analytics(undefined as any)).toThrowError(
"You must pass your June workspace's write key.",
);
});
it("should not be able to create a new instance with object writeKey", () => {
// given
// then
expect(() => new Analytics({} as any)).toThrowError(
"You must pass your June workspace's write key.",
);
});
it("should track events and send it to api.june.so/sdk/batch", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const analytics = new Analytics("writeKey", {
maxEventsInBatch: 1,
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track({
userId: "userId",
event: "event",
properties: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(1);
const { url, batch } = extractData(fetchAction);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(1);
expect(batch[0]).toMatchObject({
type: "track",
userId: "userId",
properties: {
property: "property",
},
});
});
it("should identify and send it to api.june.so/sdk/batch", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const analytics = new Analytics("writeKey", {
maxEventsInBatch: 1,
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.identify({
userId: "userId",
traits: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(1);
const { url, batch } = extractData(fetchAction);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(1);
expect(batch[0]).toMatchObject({
type: "identify",
userId: "userId",
traits: {
property: "property",
},
});
});
it("should group and send it to api.june.so/sdk/batch", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const analytics = new Analytics("writeKey", {
maxEventsInBatch: 1,
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.group({
groupId: "groupId",
userId: "userId",
traits: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(1);
const { url, batch } = extractData(fetchAction);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(1);
expect(batch[0]).toMatchObject({
type: "group",
groupId: "groupId",
userId: "userId",
traits: {
property: "property",
},
});
});
it("should flush events and send it to api.june.so/sdk/batch", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
new Array(10).fill(0).forEach(() =>
analytics.track({
userId: "userId",
event: "event",
properties: {
property: "property",
},
}),
);
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(1);
const { url, batch } = extractData(fetchAction);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(10);
});
it("should send different event names", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track({
userId: "userId",
event: "event",
properties: {
property: "property",
},
});
analytics.track({
userId: "userId",
event: "event2",
properties: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(1);
const { url, batch } = extractData(fetchAction);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(2);
expect(batch[0]).toMatchObject({
type: "track",
userId: "userId",
event: "event",
properties: {
property: "property",
},
});
expect(batch[1]).toMatchObject({
type: "track",
userId: "userId",
event: "event2",
properties: {
property: "property",
},
});
});
it("should retry failed calls", async () => {
// given
const fetchAction = jest
.fn()
.mockResolvedValueOnce({ status: 500 })
.mockResolvedValueOnce({ status: 200 });
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track({
userId: "userId",
event: "event",
properties: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(2);
const { url, batch } = extractData(fetchAction, 1);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(1);
expect(batch[0]).toMatchObject({
type: "track",
userId: "userId",
properties: {
property: "property",
},
});
});
it("should retry to maxRetries if not successful", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 500 });
const analytics = new Analytics("writeKey", {
maxRetries: 3,
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track({
userId: "userId",
event: "event",
properties: {
property: "property",
},
});
await analytics.closeAndFlush();
// then
expect(fetchAction).toHaveBeenCalledTimes(4);
const { url, batch } = extractData(fetchAction, 3);
expect(url).toBe("https://api.june.so/sdk/batch");
expect(batch).toHaveLength(1);
expect(batch[0]).toMatchObject({
type: "track",
userId: "userId",
properties: {
property: "property",
},
});
});
it("should call callback function on success", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 200 });
const callback = jest.fn();
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track(
{
userId: "userId",
event: "event",
properties: {
property: "property",
},
},
callback,
);
await analytics.closeAndFlush();
// then
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(
undefined,
expect.objectContaining({
attempts: 1,
event: expect.objectContaining({
userId: "userId",
event: "event",
type: "track",
}),
}),
);
});
it("should call callback function on failure", async () => {
// given
const fetchAction = jest.fn().mockResolvedValue({ status: 500 });
const callback = jest.fn();
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track(
{
userId: "userId",
event: "event",
properties: {
property: "property",
},
},
callback,
);
await analytics.closeAndFlush();
// then
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(expect.any(Error), expect.anything());
});
it("should handle client throwing error", async () => {
// given
const error = new Error("some random failure");
const fetchAction = jest.fn().mockRejectedValue(error);
const callback = jest.fn();
const analytics = new Analytics("writeKey", {
httpClient: new FetchHTTPClient(fetchAction),
});
// when
analytics.track(
{
userId: "userId",
event: "event",
properties: {
property: "property",
},
},
callback,
);
await analytics.closeAndFlush();
// then
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(error, expect.anything());
});
});