| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | // fixture is carlos logs output as it prints it: its own prefix, then the |
| 12 | // app's JSON line. Two people in one call, a call before it, a problem |
| 13 | // report in two parts, and a text line the summary must skip. |
| 14 | const fixture = `2026-10-03 14:39:30 [app] calls.bds.oncarlos.com time=2026-10-03T14:39:30.000Z level=INFO msg="rastrillo: serving" |
| 15 | 2026-10-03 14:40:00 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:40:00Z","level":"INFO","msg":"calls: quality","call":"oldcalloldcall22","peer":"zzzzzzzzzzzzzzzz","app":"web","seconds":30,"report":{"seconds":30}} |
| 16 | 2026-10-03 14:41:00 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:00Z","level":"INFO","msg":"calls: quality","call":"callcallcallcall","peer":"aaaaaaaaaaaaaaaa","app":"mac","version":"0.4.9","os":"macos","osVersion":"26.0.1","final":false,"seconds":30,"report":{"seconds":30,"devices":{"input":"bluetooth","output":"built-in"},"peers":[{"peer":"bbbbbbbbbbbbbbbb","path":{"local":"srflx","remote":"relay","protocol":"udp"},"rtt":48,"in":{"audio":{"packets":1490,"lost":10,"samples":1440000,"concealed":14400},"video":{"packets":900,"lost":100,"freezes":2,"freezeSeconds":1.5}},"events":{"reconnecting":1,"reconnectingSeconds":4.2}}]}} |
| 17 | 2026-10-03 14:41:05 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:05Z","level":"INFO","msg":"calls: quality","call":"callcallcallcall","peer":"bbbbbbbbbbbbbbbb","app":"web","version":"de04e2d","browser":"chrome","browserVersion":"141.0","os":"windows","osVersion":"10.0","seconds":30,"report":{"seconds":30,"peers":[{"peer":"aaaaaaaaaaaaaaaa","path":{"local":"relay","remote":"srflx","protocol":"tls"},"rtt":180,"in":{"audio":{"packets":1500,"lost":0}},"events":{"cantHearSeconds":3}}]}} |
| 18 | 2026-10-03 14:41:30 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:30Z","level":"INFO","msg":"calls: quality","call":"callcallcallcall","peer":"aaaaaaaaaaaaaaaa","app":"mac","version":"0.4.9","os":"macos","osVersion":"26.0.1","final":true,"seconds":30,"report":{"seconds":30,"peers":[{"peer":"bbbbbbbbbbbbbbbb","path":{"local":"host","remote":"host","protocol":"udp"},"rtt":20}]}} |
| 19 | 2026-10-03 14:41:20 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:20Z","level":"WARN","msg":"calls: problem report","ref":"K7Q2M9PD","call":"callcallcallcall","peer":"aaaaaaaaaaaaaaaa","app":"mac","version":"0.4.9","os":"macos","live":true,"note":"video froze","parts":2} |
| 20 | 2026-10-03 14:41:20 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:20Z","level":"WARN","msg":"calls: problem report diagnostics","ref":"K7Q2M9PD","call":"callcallcallcall","peer":"aaaaaaaaaaaaaaaa","part":2,"of":2,"text":"second half\n"} |
| 21 | 2026-10-03 14:41:20 [app] calls.bds.oncarlos.com {"time":"2026-10-03T14:41:20Z","level":"WARN","msg":"calls: problem report diagnostics","ref":"K7Q2M9PD","call":"callcallcallcall","peer":"aaaaaaaaaaaaaaaa","part":1,"of":2,"text":"first half, "} |
| 22 | ` |
| 23 | |
| 24 | func runOn(t *testing.T, args ...string) string { |
| 25 | t.Helper() |
| 26 | path := filepath.Join(t.TempDir(), "logs") |
| 27 | if err := os.WriteFile(path, []byte(fixture), 0o600); err != nil { |
| 28 | t.Fatal(err) |
| 29 | } |
| 30 | f, err := os.Open(path) |
| 31 | if err != nil { |
| 32 | t.Fatal(err) |
| 33 | } |
| 34 | defer f.Close() |
| 35 | var out bytes.Buffer |
| 36 | if err := run(append([]string{"--zone", "Europe/Dublin"}, args...), f, &out); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | return out.String() |
| 40 | } |
| 41 | |
| 42 | func TestSummaryOfOneCall(t *testing.T) { |
| 43 | out := runOn(t, "--at", "2026-10-03 15:41") |
| 44 | t.Log("\n" + out) |
| 45 | for _, want := range []string{ |
| 46 | "Call callcallcallcall 2026-10-03 15:41:00 – 15:41:30 Europe/Dublin (30s), 2 people, 1 problem report(s)", |
| 47 | "aaaaaa mac 0.4.9 macos 26.0.1 2 reports (input bluetooth, output built-in)", |
| 48 | "bbbbbb web de04e2d chrome 141.0 on windows 10.0 1 reports", |
| 49 | "aaaaaa ← bbbbbb worst loss 10.0% (video), worst RTT 48 ms, freezes 2 (1.5 s), audio concealed 1.00%, path host→host udp", |
| 50 | "relay 50%", |
| 51 | "bbbbbb ← aaaaaa worst loss 0.0%, worst RTT 180 ms, freezes 0 (0.0 s), path relay→srflx tls, relay 100%", |
| 52 | `15:41:00 aaaaaa: "Reconnecting to bbbbbb…" (4.2 s)`, |
| 53 | `15:41:05 bbbbbb: "aaaaaa can't hear you yet" (3.0 s)`, |
| 54 | "15:41:30 aaaaaa left (final report)", |
| 55 | `Problem K7Q2M9PD at 15:41:20 from aaaaaa (mac): "video froze"`, |
| 56 | } { |
| 57 | if !strings.Contains(out, want) { |
| 58 | t.Errorf("missing %q", want) |
| 59 | } |
| 60 | } |
| 61 | if strings.Contains(out, "oldcall") { |
| 62 | t.Error("--at showed a call that had ended by then") |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | func TestSummaryByCallTagAndAProblemReport(t *testing.T) { |
| 67 | if out := runOn(t, "--call", "oldcalloldcall22"); !strings.Contains(out, "Call oldcalloldcall22") || strings.Contains(out, "callcallcallcall") { |
| 68 | t.Fatalf("--call:\n%s", out) |
| 69 | } |
| 70 | out := runOn(t, "--problem", "k7q2m9pd") |
| 71 | if !strings.Contains(out, "note: video froze") || !strings.Contains(out, "first half, second half") { |
| 72 | t.Fatalf("--problem:\n%s", out) |
| 73 | } |
| 74 | } |
| 75 | |