xref: /aosp_15_r20/external/pigweed/pw_web/log-viewer/test/log-source.test.js (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2023 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker//
3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker// the License at
6*61c4878aSAndroid Build Coastguard Worker//
7*61c4878aSAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker//
9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker// the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Workerimport { expect } from '@open-wc/testing';
16*61c4878aSAndroid Build Coastguard Workerimport { spy, match } from 'sinon';
17*61c4878aSAndroid Build Coastguard Workerimport { LogSource } from '../src/log-source';
18*61c4878aSAndroid Build Coastguard Workerimport { BrowserLogSource } from '../src/custom/browser-log-source';
19*61c4878aSAndroid Build Coastguard Workerimport { Level } from '../src/shared/interfaces';
20*61c4878aSAndroid Build Coastguard Worker
21*61c4878aSAndroid Build Coastguard Workerdescribe('log-source', () => {
22*61c4878aSAndroid Build Coastguard Worker  let logSourceA, logSourceB;
23*61c4878aSAndroid Build Coastguard Worker  const logEntry = {
24*61c4878aSAndroid Build Coastguard Worker    level: 'INFO',
25*61c4878aSAndroid Build Coastguard Worker    timestamp: new Date(Date.now()),
26*61c4878aSAndroid Build Coastguard Worker    fields: [{ key: 'message', value: 'Log message' }],
27*61c4878aSAndroid Build Coastguard Worker  };
28*61c4878aSAndroid Build Coastguard Worker
29*61c4878aSAndroid Build Coastguard Worker  beforeEach(() => {
30*61c4878aSAndroid Build Coastguard Worker    logSourceA = new LogSource('Log Source A');
31*61c4878aSAndroid Build Coastguard Worker    logSourceB = new LogSource('Log Source B');
32*61c4878aSAndroid Build Coastguard Worker  });
33*61c4878aSAndroid Build Coastguard Worker
34*61c4878aSAndroid Build Coastguard Worker  afterEach(() => {
35*61c4878aSAndroid Build Coastguard Worker    logSourceA = null;
36*61c4878aSAndroid Build Coastguard Worker    logSourceB = null;
37*61c4878aSAndroid Build Coastguard Worker  });
38*61c4878aSAndroid Build Coastguard Worker
39*61c4878aSAndroid Build Coastguard Worker  it('emits events to registered listeners', () => {
40*61c4878aSAndroid Build Coastguard Worker    const eventType = 'log-entry';
41*61c4878aSAndroid Build Coastguard Worker    let receivedData = null;
42*61c4878aSAndroid Build Coastguard Worker
43*61c4878aSAndroid Build Coastguard Worker    const listener = (event) => {
44*61c4878aSAndroid Build Coastguard Worker      receivedData = event.data;
45*61c4878aSAndroid Build Coastguard Worker    };
46*61c4878aSAndroid Build Coastguard Worker
47*61c4878aSAndroid Build Coastguard Worker    logSourceA.addEventListener(eventType, listener);
48*61c4878aSAndroid Build Coastguard Worker    logSourceA.publishLogEntry(logEntry);
49*61c4878aSAndroid Build Coastguard Worker
50*61c4878aSAndroid Build Coastguard Worker    expect(receivedData).to.equal(logEntry);
51*61c4878aSAndroid Build Coastguard Worker  });
52*61c4878aSAndroid Build Coastguard Worker
53*61c4878aSAndroid Build Coastguard Worker  it("logs aren't dropped at high read frequencies", async () => {
54*61c4878aSAndroid Build Coastguard Worker    const numLogs = 10;
55*61c4878aSAndroid Build Coastguard Worker    const logEntries = [];
56*61c4878aSAndroid Build Coastguard Worker    const eventType = 'log-entry';
57*61c4878aSAndroid Build Coastguard Worker    const listener = () => {
58*61c4878aSAndroid Build Coastguard Worker      // Simulate a slow listener
59*61c4878aSAndroid Build Coastguard Worker      return new Promise((resolve) => {
60*61c4878aSAndroid Build Coastguard Worker        setTimeout(() => {
61*61c4878aSAndroid Build Coastguard Worker          resolve();
62*61c4878aSAndroid Build Coastguard Worker        }, 100);
63*61c4878aSAndroid Build Coastguard Worker      });
64*61c4878aSAndroid Build Coastguard Worker    };
65*61c4878aSAndroid Build Coastguard Worker
66*61c4878aSAndroid Build Coastguard Worker    logSourceA.addEventListener(eventType, listener);
67*61c4878aSAndroid Build Coastguard Worker
68*61c4878aSAndroid Build Coastguard Worker    const emittedLogs = [];
69*61c4878aSAndroid Build Coastguard Worker
70*61c4878aSAndroid Build Coastguard Worker    for (let i = 0; i < numLogs; i++) {
71*61c4878aSAndroid Build Coastguard Worker      logEntries.push(logEntry);
72*61c4878aSAndroid Build Coastguard Worker
73*61c4878aSAndroid Build Coastguard Worker      await logSourceA.publishLogEntry(logEntry);
74*61c4878aSAndroid Build Coastguard Worker      emittedLogs.push(logEntry);
75*61c4878aSAndroid Build Coastguard Worker    }
76*61c4878aSAndroid Build Coastguard Worker
77*61c4878aSAndroid Build Coastguard Worker    await new Promise((resolve) => setTimeout(resolve, 200));
78*61c4878aSAndroid Build Coastguard Worker
79*61c4878aSAndroid Build Coastguard Worker    expect(emittedLogs).to.deep.equal(logEntries);
80*61c4878aSAndroid Build Coastguard Worker  });
81*61c4878aSAndroid Build Coastguard Worker
82*61c4878aSAndroid Build Coastguard Worker  it('throws an error for incorrect log entry structure', async () => {
83*61c4878aSAndroid Build Coastguard Worker    const incorrectLogEntry = {
84*61c4878aSAndroid Build Coastguard Worker      fields: [{ key: 'message', value: 'Log entry without timestamp' }],
85*61c4878aSAndroid Build Coastguard Worker    };
86*61c4878aSAndroid Build Coastguard Worker
87*61c4878aSAndroid Build Coastguard Worker    try {
88*61c4878aSAndroid Build Coastguard Worker      await logSourceA.publishLogEntry(incorrectLogEntry);
89*61c4878aSAndroid Build Coastguard Worker    } catch (error) {
90*61c4878aSAndroid Build Coastguard Worker      expect(error.message).to.equal('Invalid log entry structure');
91*61c4878aSAndroid Build Coastguard Worker    }
92*61c4878aSAndroid Build Coastguard Worker  });
93*61c4878aSAndroid Build Coastguard Worker
94*61c4878aSAndroid Build Coastguard Worker  it('converts severity fields to level', async () => {
95*61c4878aSAndroid Build Coastguard Worker    const severityLogEntry = {
96*61c4878aSAndroid Build Coastguard Worker      severity: 'INFO',
97*61c4878aSAndroid Build Coastguard Worker      timestamp: new Date(Date.now()),
98*61c4878aSAndroid Build Coastguard Worker      fields: [
99*61c4878aSAndroid Build Coastguard Worker        { key: 'message', value: 'Log message' },
100*61c4878aSAndroid Build Coastguard Worker        { key: 'severity', value: 'INFO' },
101*61c4878aSAndroid Build Coastguard Worker      ],
102*61c4878aSAndroid Build Coastguard Worker    };
103*61c4878aSAndroid Build Coastguard Worker
104*61c4878aSAndroid Build Coastguard Worker    const eventType = 'log-entry';
105*61c4878aSAndroid Build Coastguard Worker    let receivedData = null;
106*61c4878aSAndroid Build Coastguard Worker
107*61c4878aSAndroid Build Coastguard Worker    const listener = (event) => {
108*61c4878aSAndroid Build Coastguard Worker      receivedData = event.data;
109*61c4878aSAndroid Build Coastguard Worker    };
110*61c4878aSAndroid Build Coastguard Worker    logSourceA.addEventListener(eventType, listener);
111*61c4878aSAndroid Build Coastguard Worker    logSourceA.publishLogEntry(severityLogEntry);
112*61c4878aSAndroid Build Coastguard Worker    expect(receivedData.severity).to.equal(undefined);
113*61c4878aSAndroid Build Coastguard Worker    expect(receivedData.level).to.equal('INFO');
114*61c4878aSAndroid Build Coastguard Worker    expect(receivedData.fields[2].key).to.equal('level');
115*61c4878aSAndroid Build Coastguard Worker  });
116*61c4878aSAndroid Build Coastguard Worker});
117*61c4878aSAndroid Build Coastguard Worker
118*61c4878aSAndroid Build Coastguard Workerdescribe('browser-log-source', () => {
119*61c4878aSAndroid Build Coastguard Worker  let browserLogSource;
120*61c4878aSAndroid Build Coastguard Worker  let originalConsoleMethods;
121*61c4878aSAndroid Build Coastguard Worker
122*61c4878aSAndroid Build Coastguard Worker  beforeEach(() => {
123*61c4878aSAndroid Build Coastguard Worker    originalConsoleMethods = {
124*61c4878aSAndroid Build Coastguard Worker      log: console.log,
125*61c4878aSAndroid Build Coastguard Worker      info: console.info,
126*61c4878aSAndroid Build Coastguard Worker      warn: console.warn,
127*61c4878aSAndroid Build Coastguard Worker      error: console.error,
128*61c4878aSAndroid Build Coastguard Worker      debug: console.debug,
129*61c4878aSAndroid Build Coastguard Worker    };
130*61c4878aSAndroid Build Coastguard Worker    browserLogSource = new BrowserLogSource();
131*61c4878aSAndroid Build Coastguard Worker    browserLogSource.start();
132*61c4878aSAndroid Build Coastguard Worker    browserLogSource.publishLogEntry = spy();
133*61c4878aSAndroid Build Coastguard Worker  });
134*61c4878aSAndroid Build Coastguard Worker
135*61c4878aSAndroid Build Coastguard Worker  afterEach(() => {
136*61c4878aSAndroid Build Coastguard Worker    browserLogSource.stop();
137*61c4878aSAndroid Build Coastguard Worker
138*61c4878aSAndroid Build Coastguard Worker    console.log = originalConsoleMethods.log;
139*61c4878aSAndroid Build Coastguard Worker    console.info = originalConsoleMethods.info;
140*61c4878aSAndroid Build Coastguard Worker    console.warn = originalConsoleMethods.warn;
141*61c4878aSAndroid Build Coastguard Worker    console.error = originalConsoleMethods.error;
142*61c4878aSAndroid Build Coastguard Worker    console.debug = originalConsoleMethods.debug;
143*61c4878aSAndroid Build Coastguard Worker  });
144*61c4878aSAndroid Build Coastguard Worker
145*61c4878aSAndroid Build Coastguard Worker  it('captures and formats console.log messages with substitutions correctly', () => {
146*61c4878aSAndroid Build Coastguard Worker    browserLogSource.publishLogEntry.resetHistory();
147*61c4878aSAndroid Build Coastguard Worker
148*61c4878aSAndroid Build Coastguard Worker    console.log("Hello, %s. You've called me %d times.", 'Alice', 5);
149*61c4878aSAndroid Build Coastguard Worker    const expectedMessage = "Hello, Alice. You've called me 5 times.";
150*61c4878aSAndroid Build Coastguard Worker
151*61c4878aSAndroid Build Coastguard Worker    expect(browserLogSource.publishLogEntry.calledOnce).to.be.true;
152*61c4878aSAndroid Build Coastguard Worker
153*61c4878aSAndroid Build Coastguard Worker    const callArgs = browserLogSource.publishLogEntry.getCall(0).args[0];
154*61c4878aSAndroid Build Coastguard Worker    expect(callArgs.level).to.equal(Level.INFO);
155*61c4878aSAndroid Build Coastguard Worker
156*61c4878aSAndroid Build Coastguard Worker    const messageField = callArgs.fields.find(
157*61c4878aSAndroid Build Coastguard Worker      (field) => field.key === 'message',
158*61c4878aSAndroid Build Coastguard Worker    );
159*61c4878aSAndroid Build Coastguard Worker    expect(messageField).to.exist;
160*61c4878aSAndroid Build Coastguard Worker    expect(messageField.value).to.equal(expectedMessage);
161*61c4878aSAndroid Build Coastguard Worker  });
162*61c4878aSAndroid Build Coastguard Worker
163*61c4878aSAndroid Build Coastguard Worker  ['log', 'info', 'warn', 'error', 'debug'].forEach((method) => {
164*61c4878aSAndroid Build Coastguard Worker    it(`captures and formats console.${method} messages`, () => {
165*61c4878aSAndroid Build Coastguard Worker      const expectedLevel = mapMethodToLevel(method);
166*61c4878aSAndroid Build Coastguard Worker
167*61c4878aSAndroid Build Coastguard Worker      // For test, log-source.test.js:XXX needs to match next line number
168*61c4878aSAndroid Build Coastguard Worker      console[method]('Test message (%s)', method);
169*61c4878aSAndroid Build Coastguard Worker      expect(browserLogSource.publishLogEntry).to.have.been.calledWithMatch({
170*61c4878aSAndroid Build Coastguard Worker        timestamp: match.instanceOf(Date),
171*61c4878aSAndroid Build Coastguard Worker        level: expectedLevel,
172*61c4878aSAndroid Build Coastguard Worker        fields: [
173*61c4878aSAndroid Build Coastguard Worker          { key: 'level', value: expectedLevel },
174*61c4878aSAndroid Build Coastguard Worker          { key: 'time', value: match.typeOf('string') },
175*61c4878aSAndroid Build Coastguard Worker          { key: 'message', value: `Test message (${method})` },
176*61c4878aSAndroid Build Coastguard Worker          { key: 'file', value: 'log-source.test.js:168' },
177*61c4878aSAndroid Build Coastguard Worker        ],
178*61c4878aSAndroid Build Coastguard Worker      });
179*61c4878aSAndroid Build Coastguard Worker    });
180*61c4878aSAndroid Build Coastguard Worker  });
181*61c4878aSAndroid Build Coastguard Worker
182*61c4878aSAndroid Build Coastguard Worker  function mapMethodToLevel(method) {
183*61c4878aSAndroid Build Coastguard Worker    switch (method) {
184*61c4878aSAndroid Build Coastguard Worker      case 'log':
185*61c4878aSAndroid Build Coastguard Worker      case 'info':
186*61c4878aSAndroid Build Coastguard Worker        return Level.INFO;
187*61c4878aSAndroid Build Coastguard Worker      case 'warn':
188*61c4878aSAndroid Build Coastguard Worker        return Level.WARNING;
189*61c4878aSAndroid Build Coastguard Worker      case 'error':
190*61c4878aSAndroid Build Coastguard Worker        return Level.ERROR;
191*61c4878aSAndroid Build Coastguard Worker      case 'debug':
192*61c4878aSAndroid Build Coastguard Worker        return Level.DEBUG;
193*61c4878aSAndroid Build Coastguard Worker      default:
194*61c4878aSAndroid Build Coastguard Worker        return Level.INFO;
195*61c4878aSAndroid Build Coastguard Worker    }
196*61c4878aSAndroid Build Coastguard Worker  }
197*61c4878aSAndroid Build Coastguard Worker
198*61c4878aSAndroid Build Coastguard Worker  it('captures and formats multiple arguments correctly', () => {
199*61c4878aSAndroid Build Coastguard Worker    console.log('This is a test', 42, { type: 'answer' });
200*61c4878aSAndroid Build Coastguard Worker
201*61c4878aSAndroid Build Coastguard Worker    const expectedMessage = 'This is a test 42 {"type":"answer"}';
202*61c4878aSAndroid Build Coastguard Worker
203*61c4878aSAndroid Build Coastguard Worker    expect(browserLogSource.publishLogEntry.calledOnce).to.be.true;
204*61c4878aSAndroid Build Coastguard Worker    const callArgs = browserLogSource.publishLogEntry.getCall(0).args[0];
205*61c4878aSAndroid Build Coastguard Worker    expect(callArgs.level).to.equal(Level.INFO);
206*61c4878aSAndroid Build Coastguard Worker
207*61c4878aSAndroid Build Coastguard Worker    const messageField = callArgs.fields.find(
208*61c4878aSAndroid Build Coastguard Worker      (field) => field.key === 'message',
209*61c4878aSAndroid Build Coastguard Worker    );
210*61c4878aSAndroid Build Coastguard Worker    expect(messageField).to.exist;
211*61c4878aSAndroid Build Coastguard Worker    expect(messageField.value).to.equal(expectedMessage);
212*61c4878aSAndroid Build Coastguard Worker  });
213*61c4878aSAndroid Build Coastguard Worker
214*61c4878aSAndroid Build Coastguard Worker  it('restores original console methods after stop is called', () => {
215*61c4878aSAndroid Build Coastguard Worker    browserLogSource.stop();
216*61c4878aSAndroid Build Coastguard Worker    expect(console.log).to.equal(originalConsoleMethods.log);
217*61c4878aSAndroid Build Coastguard Worker    expect(console.info).to.equal(originalConsoleMethods.info);
218*61c4878aSAndroid Build Coastguard Worker    expect(console.warn).to.equal(originalConsoleMethods.warn);
219*61c4878aSAndroid Build Coastguard Worker    expect(console.error).to.equal(originalConsoleMethods.error);
220*61c4878aSAndroid Build Coastguard Worker    expect(console.debug).to.equal(originalConsoleMethods.debug);
221*61c4878aSAndroid Build Coastguard Worker  });
222*61c4878aSAndroid Build Coastguard Worker});
223