Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {
TelemetryEngine,
TelemetryPromptEngine,
Trigger,
TRIGGER_CHECK_STRATEGIES,
normalizeMetadata,
submitTelemetryResult,
} = ChromeUtils.importESModule(
"moz-src:///browser/components/aiwindow/models/TelemetryUtils.sys.mjs"
);
const { openAIEngine } = ChromeUtils.importESModule(
"moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
);
function makeConversation({
turnIndex = 0,
uniformSample = false,
telemetryTriggers = new Set(),
} = {}) {
return {
_telemetryUniformSample: uniformSample,
_checkedTelemetryTriggers: telemetryTriggers,
currentTurnIndex() {
return turnIndex;
},
getMessagesInOpenAiFormat() {
return [];
},
};
}
const LONG_CONVERSATION = "long_conversation";
const UNIFORM_SAMPLE = "uniform_sample";
add_task(async function test_Trigger_stores_name_and_samplingProbability() {
const trigger = new Trigger("my_trigger", () => true, 0.75, "a description");
Assert.equal(trigger.name, "my_trigger", "name should be stored");
Assert.equal(
trigger.samplingProbability,
0.75,
"samplingProbability should be stored"
);
Assert.equal(
trigger.description,
"a description",
"description should be stored"
);
});
add_task(async function test_Trigger_default_samplingProbability_is_one() {
const trigger = new Trigger("test_trigger", () => true);
Assert.equal(
trigger.samplingProbability,
1.0,
"default samplingProbability should be 1.0"
);
});
add_task(
async function test_Trigger_check_receives_conversation_and_returns_true() {
const conversation = makeConversation({ uniformSample: true });
const trigger = new Trigger(
"test_trigger",
conv => conv._telemetryUniformSample === true,
1.0
);
Assert.ok(
trigger.check(conversation),
"check should receive the conversation and return true"
);
}
);
add_task(
async function test_Trigger_check_returns_false_when_condition_unmet() {
const conversation = makeConversation({ uniformSample: false });
const trigger = new Trigger(
"test_trigger",
conv => conv._telemetryUniformSample === true,
1.0
);
Assert.ok(
!trigger.check(conversation),
"check should return false when condition is not met"
);
}
);
add_task(async function test_getTriggers_uniform_sample_fires_when_sampled() {
const conversation = makeConversation({ turnIndex: 0, uniformSample: false });
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
UNIFORM_SAMPLE,
conv => TRIGGER_CHECK_STRATEGIES[UNIFORM_SAMPLE](conv, {}),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
conversation._telemetryUniformSample,
"_telemetryUniformSample should be set to True"
);
Assert.ok(
triggers.some(t => t.name === UNIFORM_SAMPLE),
`${UNIFORM_SAMPLE} should fire`
);
} finally {
sb.restore();
}
});
add_task(
async function test_getTriggers_uniform_sample_at_turn_fires_at_correct_turn() {
const turn = 2;
const conversation = makeConversation({
turnIndex: turn,
uniformSample: true,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
"uniform_sample_turn2",
conv =>
TRIGGER_CHECK_STRATEGIES.uniform_sample_at_turn(conv, { turn }),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
triggers.some(t => t.name === "uniform_sample_turn2"),
"uniform_sample_turn2 should fire at turn 2 when sampled"
);
} finally {
sb.restore();
}
}
);
add_task(
async function test_getTriggers_uniform_sample_turn2_does_not_fire_without_uniform_sample() {
const turn = 2;
const conversation = makeConversation({
turnIndex: turn,
uniformSample: false,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
"uniform_sample_turn2",
conv =>
TRIGGER_CHECK_STRATEGIES.uniform_sample_at_turn(conv, { turn }),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
!triggers.some(t => t.name === "uniform_sample_turn2"),
"uniform_sample_turn2 should not fire when _telemetryUniformSample is false"
);
Assert.ok(
!conversation._telemetryUniformSample,
"_telemetryUniformSample should not be set to True at turn 2"
);
} finally {
sb.restore();
}
}
);
add_task(
async function test_getTriggers_uniform_sample_at_turn_does_not_fire_at_wrong_turn() {
const turn = 2;
const conversation = makeConversation({
turnIndex: turn + 1,
uniformSample: true,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
"uniform_sample_turn2",
conv =>
TRIGGER_CHECK_STRATEGIES.uniform_sample_at_turn(conv, { turn }),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
!triggers.some(t => t.name === "uniform_sample_turn2"),
"uniform_sample_turn2 should not fire at turn 3"
);
} finally {
sb.restore();
}
}
);
add_task(async function test_getTriggers_min_turns_fires_at_threshold() {
const conversation = makeConversation({
turnIndex: 10,
uniformSample: false,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
LONG_CONVERSATION,
conv => TRIGGER_CHECK_STRATEGIES.min_turns(conv, { minTurns: 10 }),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
triggers.some(t => t.name === LONG_CONVERSATION),
`${LONG_CONVERSATION} should fire at turn 10`
);
Assert.ok(
conversation._checkedTelemetryTriggers.has(LONG_CONVERSATION),
`${LONG_CONVERSATION} should be included in _checkedTelemetryTriggers`
);
} finally {
sb.restore();
}
});
add_task(
async function test_getTriggers_min_turns_does_not_fire_below_threshold() {
const conversation = makeConversation({
turnIndex: 9,
uniformSample: false,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
LONG_CONVERSATION,
conv => TRIGGER_CHECK_STRATEGIES.min_turns(conv, { minTurns: 10 }),
1.0
),
];
});
const triggers = await engine.getTriggers(conversation);
Assert.ok(
!triggers.some(t => t.name === LONG_CONVERSATION),
`${LONG_CONVERSATION} should not fire before turn 10`
);
Assert.ok(
!conversation._checkedTelemetryTriggers.has(LONG_CONVERSATION),
`${LONG_CONVERSATION} should be not included in _checkedTelemetryTriggers before turn 10`
);
} finally {
sb.restore();
}
}
);
add_task(
async function test_getTriggers_trigger_does_not_fire_when_random_exceeds_probability() {
const turn = 10;
const conversation = makeConversation({
turnIndex: turn,
uniformSample: false,
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
LONG_CONVERSATION,
conv => TRIGGER_CHECK_STRATEGIES.min_turns(conv, { minTurns: 10 }),
0.5
),
];
});
sb.stub(engine, "_getRandom").returns(0.9);
const triggers = await engine.getTriggers(conversation);
Assert.ok(
!triggers.some(t => t.name === LONG_CONVERSATION),
"trigger should not fire when Math.random() exceeds samplingProbability"
);
Assert.ok(
conversation._checkedTelemetryTriggers.has(LONG_CONVERSATION),
`${LONG_CONVERSATION} should be included in _checkedTelemetryTriggers`
);
} finally {
sb.restore();
}
}
);
add_task(
async function test_getTriggers_trigger_does_not_refire_for_same_conversation() {
const turn = 10;
const conversation = makeConversation({
turnIndex: turn,
uniformSample: true,
});
const conversation2 = makeConversation({
turnIndex: turn + 1,
uniformSample: true,
telemetryTriggers: new Set([LONG_CONVERSATION]),
});
const engine = new TelemetryEngine();
const sb = sinon.createSandbox();
try {
sb.stub(engine, "getTriggerDefinitions").callsFake(async function () {
engine._triggers = [
new Trigger(
LONG_CONVERSATION,
conv => TRIGGER_CHECK_STRATEGIES.min_turns(conv, { minTurns: 10 }),
0.5
),
];
});
const randomStub = sb.stub(engine, "_getRandom").returns(0.9);
const triggers1 = await engine.getTriggers(conversation);
Assert.ok(
!triggers1.some(t => t.name === LONG_CONVERSATION),
`${LONG_CONVERSATION} should not fire when sampling probability not met`
);
Assert.ok(
conversation._checkedTelemetryTriggers.has(LONG_CONVERSATION),
`${LONG_CONVERSATION} should be included in _checkedTelemetryTriggers`
);
randomStub.returns(0.3);
const triggers2 = await engine.getTriggers(conversation2);
Assert.ok(
!triggers2.some(t => t.name === LONG_CONVERSATION),
`${LONG_CONVERSATION} should not refire on a second call for the same conversation`
);
Assert.ok(
conversation2._checkedTelemetryTriggers.has(LONG_CONVERSATION),
`${LONG_CONVERSATION} should be included in _checkedTelemetryTriggers`
);
} finally {
sb.restore();
}
}
);
add_task(async function test_runTelemetry_returns_empty_for_no_triggers() {
const engine = new TelemetryEngine();
const results = await engine.runTelemetry([], makeConversation());
Assert.deepEqual(
results,
[],
"runTelemetry with no triggers should return []"
);
});
add_task(async function test_runTelemetry_runs_engine_for_matched_trigger() {
const engine = new TelemetryEngine();
const TELEMETRY_NAME = "wasSuccessful";
const sb = sinon.createSandbox();
try {
const fakeResult = {
wasSuccessful: "successful",
conversationTopic: "sports",
memoryReferenced: "no",
};
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: TELEMETRY_NAME,
triggers: [UNIFORM_SAMPLE],
output_schema: { myField: ["a", "b"] },
prompt: "...",
},
]);
const fakeEngine = { run: sb.stub().resolves(fakeResult) };
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
const trigger = new Trigger(UNIFORM_SAMPLE, () => true, 1.0);
const results = await engine.runTelemetry([trigger], makeConversation());
Assert.ok(
results.length,
"should return results when a trigger matches a prompt record"
);
Assert.ok(
results.some(r => r.telemetry_name === TELEMETRY_NAME),
`${TELEMETRY_NAME} result should be present`
);
Assert.ok(fakeEngine.run.called, "engine.run should be called");
Assert.equal(
results[0].samplingProbability,
1.0,
"result should include the samplingProbability of the matching trigger"
);
} finally {
sb.restore();
}
});
add_task(async function test_runTelemetry_deduplicates_by_telemetry_name() {
const engine = new TelemetryEngine();
const TELEMETRY_NAME = "wasSuccessful";
const sb = sinon.createSandbox();
try {
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: TELEMETRY_NAME,
triggers: [UNIFORM_SAMPLE, `${UNIFORM_SAMPLE}_turn2`],
output_schema: { myField: ["a", "b"] },
prompt: "...",
},
]);
const fakeEngine = {
run: sb.stub().resolves({ wasSuccessful: "successful" }),
};
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
const triggers = [
new Trigger(UNIFORM_SAMPLE, () => true, 1.0),
new Trigger(`${UNIFORM_SAMPLE}_turn2`, () => true, 1.0),
];
const results = await engine.runTelemetry(triggers, makeConversation());
const wasSuccessfulCount = results.filter(
r => r.telemetry_name === "wasSuccessful"
).length;
Assert.equal(
wasSuccessfulCount,
1,
"wasSuccessful prompt should run only once despite multiple matching triggers"
);
Assert.equal(
TelemetryPromptEngine.build.callCount,
1,
"build should be called only once for the deduplicated prompt"
);
} finally {
sb.restore();
}
});
add_task(
async function test_runTelemetry_samplingProbability_comes_from_first_matching_trigger() {
const engine = new TelemetryEngine();
const TELEMETRY_NAME = "wasSuccessful";
const sb = sinon.createSandbox();
try {
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: TELEMETRY_NAME,
triggers: [UNIFORM_SAMPLE, `${UNIFORM_SAMPLE}_turn2`],
output_schema: { myField: ["a", "b"] },
prompt: "...",
},
]);
const fakeEngine = {
run: sb.stub().resolves({ wasSuccessful: "successful" }),
};
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
const triggers = [
new Trigger(UNIFORM_SAMPLE, () => true, 0.3),
new Trigger(`${UNIFORM_SAMPLE}_turn2`, () => true, 0.7),
];
const results = await engine.runTelemetry(triggers, makeConversation());
Assert.equal(
results[0].samplingProbability,
0.3,
"samplingProbability should come from the first matching trigger in the record's trigger list"
);
} finally {
sb.restore();
}
}
);
add_task(
async function test_runTelemetry_does_not_run_prompt_for_unmatched_trigger() {
const engine = new TelemetryEngine();
const TELEMETRY_NAME = "wasSuccessful";
const sb = sinon.createSandbox();
try {
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: UNIFORM_SAMPLE,
triggers: [UNIFORM_SAMPLE, `${UNIFORM_SAMPLE}_turn2`],
output_schema: { myField: ["a", "b"] },
prompt: "...",
},
{
id: "my-prompt-v2",
version: "1.0",
telemetry_name: "conversationCategory",
triggers: [LONG_CONVERSATION],
output_schema: { myField: ["a", "b"] },
prompt: "...",
},
]);
const fakeEngine = { run: sb.stub().resolves({}) };
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
// long_conversation matches isLongConvo prompt, not wasSuccessful
const trigger = new Trigger(LONG_CONVERSATION, () => true, 1.0);
const results = await engine.runTelemetry([trigger], makeConversation());
Assert.ok(
!results.some(r => r.telemetry_name === TELEMETRY_NAME),
`${TELEMETRY_NAME} should not run when only ${LONG_CONVERSATION} trigger fires`
);
Assert.ok(
results.some(r => r.telemetry_name === "conversationCategory"),
`conversationCategory should run when ${LONG_CONVERSATION} trigger fires`
);
} finally {
sb.restore();
}
}
);
add_task(async function test_runTelemetryByName_happy_path() {
const engine = new TelemetryEngine();
const TELEMETRY_NAMES = ["wasSuccessful", "conversationCategory"];
const sb = sinon.createSandbox();
try {
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: "wasSuccessful",
triggers: [UNIFORM_SAMPLE, `${UNIFORM_SAMPLE}_turn2`],
output_schema: { myField: ["a", "b"] },
prompt: "...",
run_terminal: true,
},
{
id: "my-prompt-v2",
version: "1.0",
telemetry_name: "conversationCategory",
triggers: [LONG_CONVERSATION],
output_schema: { myField: ["a", "b"] },
prompt: "...",
run_terminal: true,
},
]);
sb.stub(engine, "_runPrompts").callsFake(records =>
Promise.resolve(
records.map(r => ({ telemetry_name: r.telemetry_name, result: {} }))
)
);
const fakeEngine = { run: sb.stub().resolves({}) };
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
// long_conversation matches isLongConvo prompt, not wasSuccessful
const results = await engine.runTelemetryByName(
TELEMETRY_NAMES,
makeConversation()
);
Assert.strictEqual(
results.length,
TELEMETRY_NAMES.length,
`Results should be run on all telemetry`
);
Assert.ok(
results.some(r => r.telemetry_name === TELEMETRY_NAMES[0]),
`${TELEMETRY_NAMES[0]} should be run`
);
Assert.ok(
results.some(r => r.telemetry_name === TELEMETRY_NAMES[1]),
`${TELEMETRY_NAMES[1]} should be run`
);
} finally {
sb.restore();
}
});
add_task(async function test_runTelemetryByName_dont_run_terminal() {
const engine = new TelemetryEngine();
const TELEMETRY_NAMES = ["wasSuccessful", "conversationCategory"];
const sb = sinon.createSandbox();
try {
sb.stub(engine, "_fetchRecords").resolves([
{
id: "my-prompt-v1",
version: "1.0",
telemetry_name: "wasSuccessful",
triggers: [UNIFORM_SAMPLE, `${UNIFORM_SAMPLE}_turn2`],
output_schema: { myField: ["a", "b"] },
prompt: "...",
run_terminal: true,
},
{
id: "my-prompt-v2",
version: "1.0",
telemetry_name: "conversationCategory",
triggers: [LONG_CONVERSATION],
output_schema: { myField: ["a", "b"] },
prompt: "...",
run_terminal: false,
},
]);
sb.stub(engine, "_runPrompts").callsFake(records =>
Promise.resolve(
records.map(r => ({ telemetry_name: r.telemetry_name, result: {} }))
)
);
const fakeEngine = { run: sb.stub().resolves({}) };
sb.stub(TelemetryPromptEngine, "build").resolves(fakeEngine);
// long_conversation matches isLongConvo prompt, not wasSuccessful
const results = await engine.runTelemetryByName(
TELEMETRY_NAMES,
makeConversation()
);
Assert.strictEqual(
results.length,
1,
`Results should not be run on non-terminal telemetry`
);
Assert.ok(
results.some(r => r.telemetry_name === "wasSuccessful"),
`wasSuccessful should be run`
);
Assert.ok(
!results.some(r => r.telemetry_name === "isLongConvo"),
`isLongConvo should not be run`
);
} finally {
sb.restore();
}
});
async function buildTestEngine(outputSchema) {
const sb = sinon.createSandbox();
sb.stub(openAIEngine, "_createEngine").resolves({});
try {
const engine = await TelemetryPromptEngine.build({
telemetry_name: "test",
version: "1.0",
model: "fake-model",
triggers: [UNIFORM_SAMPLE],
output_schema: outputSchema,
prompt: "test prompt {chatConversation} {fields}",
});
return { engine, sb };
} catch (e) {
sb.restore();
throw e;
}
}
add_task(async function test_verifyResult_returns_valid_values() {
const schema = {
wasSuccessful: ["successful", "not successful", "ongoing"],
memoryReferenced: ["yes", "no"],
};
const { engine, sb } = await buildTestEngine(schema);
try {
const result = engine.verifyResult({
finalOutput: JSON.stringify({
wasSuccessful: "successful",
memoryReferenced: "no",
}),
});
Assert.equal(
result.wasSuccessful,
"successful",
"valid value should be preserved"
);
Assert.equal(
result.memoryReferenced,
"no",
"valid value should be preserved"
);
} finally {
sb.restore();
}
});
add_task(
async function test_verifyResult_returns_unknown_for_invalid_field_value() {
const schema = {
wasSuccessful: ["successful", "not successful", "ongoing"],
};
const { engine, sb } = await buildTestEngine(schema);
try {
const result = engine.verifyResult({
finalOutput: JSON.stringify({ wasSuccessful: "definitely_successful" }),
});
Assert.equal(
result.wasSuccessful,
"unknown",
"value not in schema should be replaced with 'unknown'"
);
} finally {
sb.restore();
}
}
);
add_task(async function test_verifyResult_returns_unknown_for_missing_field() {
const schema = {
wasSuccessful: ["successful", "not successful", "ongoing"],
memoryReferenced: ["yes", "no"],
};
const { engine, sb } = await buildTestEngine(schema);
try {
const result = engine.verifyResult({
finalOutput: JSON.stringify({ wasSuccessful: "successful" }),
});
Assert.equal(
result.wasSuccessful,
"successful",
"present valid field should be preserved"
);
Assert.equal(
result.memoryReferenced,
"unknown",
"missing field should be 'unknown'"
);
} finally {
sb.restore();
}
});
add_task(
async function test_verifyResult_returns_all_unknown_for_invalid_json() {
const schema = {
wasSuccessful: ["successful", "not successful", "ongoing"],
memoryReferenced: ["yes", "no"],
};
const { engine, sb } = await buildTestEngine(schema);
try {
const result = engine.verifyResult({
finalOutput: "not { valid } json {{",
});
Assert.equal(
result.wasSuccessful,
"unknown",
"invalid JSON should yield 'unknown'"
);
Assert.equal(
result.memoryReferenced,
"unknown",
"invalid JSON should yield 'unknown' for all fields"
);
} finally {
sb.restore();
}
}
);
add_task(async function test_normalizeMetadata_defaults() {
const result = normalizeMetadata();
Assert.deepEqual(result, {
chat_version: "",
record_type: "",
uniform_sampling_probability: 0,
trigger_sampling_probability: 0,
triggers: "[]",
});
});
add_task(
async function test_normalizeMetadata_scales_probabilities_and_formats_triggers() {
const result = normalizeMetadata({
telemetry_version: "1",
chat_version: "chat-v2",
record_type: "terminal",
uniform_sampled: true,
uniform_sampling_probability: 0.25,
trigger_sampled: true,
trigger_sampling_probability: 0.123,
triggers: ["uniform_sample", "long_conversation"],
});
Assert.deepEqual(result, {
chat_version: "chat-v2",
record_type: "terminal",
uniform_sampling_probability: 250,
trigger_sampling_probability: 123,
triggers: JSON.stringify(["uniform_sample", "long_conversation"]),
});
}
);
add_task(
async function test_submitTelemetryResult_records_one_event_per_attribute() {
Services.fog.testResetFOG();
const conversation = {
id: "conversation-id",
currentTurnIndex() {
return 3;
},
};
submitTelemetryResult(
[
{
telemetry_name: "hello",
samplingProbability: 0.25,
result: {
was_successful: "successful",
conversation_topic: "sports",
memory_referenced: true,
},
},
],
conversation,
"fake-model",
{
chat_version: "chat-v1",
record_type: "terminal",
uniform_sampling_probability: 0.5,
triggers: ["uniform_sample"],
}
);
const events = Glean.smartWindow.llmajBasedTelemetry.testGetValue();
Assert.ok(events, "Should record llmajBasedTelemetry events");
Assert.equal(
events.length,
3,
"Should record one event per telemetry result attribute"
);
const first = events[0].extra;
Assert.equal(first.chat_id, "conversation-id");
Assert.equal(first.model, "fake-model");
Assert.equal(first.turn_number, "3");
Assert.equal(first.telemetry_name, "hello");
Assert.equal(first.chat_version, "chat-v1");
Assert.equal(first.record_type, "terminal");
Assert.equal(first.uniform_sampling_probability, "500");
Assert.equal(first.trigger_sampling_probability, "250");
Assert.equal(first.triggers, JSON.stringify(["uniform_sample"]));
Assert.equal(first.attribute_name, "was_successful");
Assert.equal(first.attribute_value, "successful");
const second = events[1].extra;
Assert.equal(second.telemetry_name, "hello");
Assert.equal(second.attribute_name, "conversation_topic");
Assert.equal(second.attribute_value, "sports");
const third = events[2].extra;
Assert.equal(third.telemetry_name, "hello");
Assert.equal(third.attribute_name, "memory_referenced");
Assert.equal(third.attribute_value, "true");
}
);