[EBT] Fix flaky test (#131494)

This commit is contained in:
Alejandro Fernández Haro 2022-05-04 12:32:18 +02:00 committed by GitHub
parent 9aeb1fb446
commit c9b424c8a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -9,6 +9,7 @@
// eslint-disable-next-line max-classes-per-file
import type { Observable } from 'rxjs';
import { BehaviorSubject, firstValueFrom, lastValueFrom, Subject } from 'rxjs';
import { fakeSchedulers } from 'rxjs-marbles/jest';
import type { MockedLogger } from '@kbn/logging-mocks';
import { loggerMock } from '@kbn/logging-mocks';
import { AnalyticsClient } from './analytics_client';
@ -17,14 +18,12 @@ import { shippersMock } from '../shippers/mocks';
import type { EventContext, TelemetryCounter } from '../events';
import { TelemetryCounterType } from '../events';
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// FLAKY: https://github.com/elastic/kibana/issues/131369
describe.skip('AnalyticsClient', () => {
describe('AnalyticsClient', () => {
let analyticsClient: AnalyticsClient;
let logger: MockedLogger;
beforeEach(() => {
jest.useFakeTimers();
logger = loggerMock.create();
analyticsClient = new AnalyticsClient({
logger,
@ -33,6 +32,10 @@ describe.skip('AnalyticsClient', () => {
});
});
afterEach(() => {
jest.useRealTimers();
});
describe('registerEventType', () => {
test('successfully registers a event type', () => {
analyticsClient.registerEventType({
@ -311,54 +314,63 @@ describe.skip('AnalyticsClient', () => {
expect(optIn).toHaveBeenCalledWith(true);
});
test('Spreads the context updates to the shipper (only after opt-in)', async () => {
const extendContextMock = jest.fn();
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
analyticsClient.optIn({ global: { enabled: true } });
await delay(10);
expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context
test(
'Spreads the context updates to the shipper (only after opt-in)',
fakeSchedulers(async (advance) => {
const extendContextMock = jest.fn();
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
analyticsClient.optIn({ global: { enabled: true } });
advance(10);
expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context
const context$ = new Subject<{ a_field: boolean }>();
analyticsClient.registerContextProvider({
name: 'contextProviderA',
schema: {
a_field: {
type: 'boolean',
_meta: {
description: 'a_field description',
const context$ = new Subject<{ a_field: boolean }>();
analyticsClient.registerContextProvider({
name: 'contextProviderA',
schema: {
a_field: {
type: 'boolean',
_meta: {
description: 'a_field description',
},
},
},
},
context$,
});
context$,
});
context$.next({ a_field: true });
expect(extendContextMock).toHaveBeenCalledWith({ a_field: true }); // After update
});
context$.next({ a_field: true });
expect(extendContextMock).toHaveBeenCalledWith({ a_field: true }); // After update
})
);
test('Does not spread the context if opt-in === false', async () => {
const extendContextMock = jest.fn();
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
analyticsClient.optIn({ global: { enabled: false } });
await delay(10);
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
});
test(
'Does not spread the context if opt-in === false',
fakeSchedulers(async (advance) => {
const extendContextMock = jest.fn();
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
analyticsClient.optIn({ global: { enabled: false } });
advance(10);
expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in
})
);
test('Handles errors in the shipper', async () => {
const extendContextMock = jest.fn().mockImplementation(() => {
throw new Error('Something went terribly wrong');
});
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
analyticsClient.optIn({ global: { enabled: true } });
await delay(10);
expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context
expect(logger.warn).toHaveBeenCalledWith(
`Shipper "${MockedShipper.shipperName}" failed to extend the context`,
expect.any(Error)
);
});
test(
'Handles errors in the shipper',
fakeSchedulers(async (advance) => {
const extendContextMock = jest.fn().mockImplementation(() => {
throw new Error('Something went terribly wrong');
});
analyticsClient.registerShipper(MockedShipper, { extendContextMock });
analyticsClient.optIn({ global: { enabled: true } });
advance(10);
expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context
expect(logger.warn).toHaveBeenCalledWith(
`Shipper "${MockedShipper.shipperName}" failed to extend the context`,
expect.any(Error)
);
})
);
});
describe('registerContextProvider', () => {
@ -819,86 +831,89 @@ describe.skip('AnalyticsClient', () => {
]);
});
test('Sends events from the internal queue when there are shippers and an opt-in response is true', async () => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
test(
'Sends events from the internal queue when there are shippers and an opt-in response is true',
fakeSchedulers(async (advance) => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// As proven in the previous test, the events are still enqueued.
// Let's register a shipper and opt-in to test the dequeue logic.
const reportEventsMock = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock });
analyticsClient.optIn({ global: { enabled: true } });
await delay(10);
// As proven in the previous test, the events are still enqueued.
// Let's register a shipper and opt-in to test the dequeue logic.
const reportEventsMock = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock });
analyticsClient.optIn({ global: { enabled: true } });
advance(10);
expect(reportEventsMock).toHaveBeenCalledTimes(2);
expect(reportEventsMock).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock).toHaveBeenCalledTimes(2);
expect(reportEventsMock).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
});
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
})
);
test('Discards events from the internal queue when there are shippers and an opt-in response is false', async () => {
const telemetryCounterPromise = lastValueFrom(
@ -942,250 +957,259 @@ describe.skip('AnalyticsClient', () => {
]);
});
test('Discards only one type of the enqueued events based on event_type config', async () => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 1), toArray()) // Waiting for 3 enqueued + 1 batch-shipped events
);
test(
'Discards only one type of the enqueued events based on event_type config',
fakeSchedulers(async (advance) => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 1), toArray()) // Waiting for 3 enqueued + 1 batch-shipped events
);
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
const reportEventsMock = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock });
analyticsClient.optIn({
global: { enabled: true },
event_types: { ['event-type-a']: { enabled: false } },
});
await delay(10);
const reportEventsMock = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock });
analyticsClient.optIn({
global: { enabled: true },
event_types: { ['event-type-a']: { enabled: false } },
});
advance(10);
expect(reportEventsMock).toHaveBeenCalledTimes(1);
expect(reportEventsMock).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock).toHaveBeenCalledTimes(1);
expect(reportEventsMock).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
// Expect 3 enqueued events, and 1 sent_to_shipper batched request
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
});
// Expect 3 enqueued events, and 1 sent_to_shipper batched request
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
})
);
test('Discards the event at the shipper level (for a specific event)', async () => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
test(
'Discards the event at the shipper level (for a specific event)',
fakeSchedulers(async (advance) => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Register 2 shippers and set 1 of them as disabled for event-type-a
const reportEventsMock1 = jest.fn();
const reportEventsMock2 = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 });
analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 });
analyticsClient.optIn({
global: { enabled: true },
event_types: {
['event-type-a']: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } },
},
});
await delay(10);
// Register 2 shippers and set 1 of them as disabled for event-type-a
const reportEventsMock1 = jest.fn();
const reportEventsMock2 = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 });
analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 });
analyticsClient.optIn({
global: { enabled: true },
event_types: {
['event-type-a']: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } },
},
});
advance(10);
expect(reportEventsMock1).toHaveBeenCalledTimes(2);
expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock2).toHaveBeenCalledTimes(1);
expect(reportEventsMock2).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock1).toHaveBeenCalledTimes(2);
expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock2).toHaveBeenCalledTimes(1);
expect(reportEventsMock2).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
});
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
})
);
test('Discards all the events at the shipper level (globally disabled)', async () => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
test(
'Discards all the events at the shipper level (globally disabled)',
fakeSchedulers(async (advance) => {
const telemetryCounterPromise = lastValueFrom(
analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events
);
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Send multiple events of 1 type to test the grouping logic as well
analyticsClient.reportEvent('event-type-a', { a_field: 'a' });
analyticsClient.reportEvent('event-type-b', { b_field: 100 });
analyticsClient.reportEvent('event-type-a', { a_field: 'b' });
// Register 2 shippers and set 1 of them as globally disabled
const reportEventsMock1 = jest.fn();
const reportEventsMock2 = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 });
analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 });
analyticsClient.optIn({
global: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } },
event_types: {
['event-type-a']: { enabled: true },
},
});
await delay(10);
// Register 2 shippers and set 1 of them as globally disabled
const reportEventsMock1 = jest.fn();
const reportEventsMock2 = jest.fn();
analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 });
analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 });
analyticsClient.optIn({
global: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } },
event_types: {
['event-type-a']: { enabled: true },
},
});
advance(10);
expect(reportEventsMock1).toHaveBeenCalledTimes(2);
expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock2).toHaveBeenCalledTimes(0);
expect(reportEventsMock1).toHaveBeenCalledTimes(2);
expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'a' },
timestamp: expect.any(String),
},
{
context: {},
event_type: 'event-type-a',
properties: { a_field: 'b' },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [
{
context: {},
event_type: 'event-type-b',
properties: { b_field: 100 },
timestamp: expect.any(String),
},
]);
expect(reportEventsMock2).toHaveBeenCalledTimes(0);
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
});
// Expect 3 enqueued events, and 2 sent_to_shipper batched requests
await expect(telemetryCounterPromise).resolves.toEqual([
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-b',
code: 'enqueued',
count: 1,
},
{
type: 'enqueued',
source: 'client',
event_type: 'event-type-a',
code: 'enqueued',
count: 1,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-a',
code: 'OK',
count: 2,
},
{
type: 'sent_to_shipper',
source: 'client',
event_type: 'event-type-b',
code: 'OK',
count: 1,
},
]);
})
);
test('Discards incoming events when opt-in response is false', async () => {
// Set OptIn and shipper first to test the "once-set up" scenario