@polderlabs/bizar 4.8.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js +656 -0
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +1 -0
- package/bizar-dash/dist/assets/index-DmpSFPJY.js +9 -0
- package/bizar-dash/dist/assets/index-DmpSFPJY.js.map +1 -0
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js +16 -0
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +1 -0
- package/bizar-dash/dist/assets/{main-DX_Jh8Wc.css → main-ZAfGKENE.css} +1 -1
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +2 -0
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-DHXXbn1A.js +1 -0
- package/bizar-dash/dist/assets/{mobile-Chvf9u_B.js.map → mobile-DHXXbn1A.js.map} +1 -1
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -3
- package/bizar-dash/dist/mobile.html +5 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/skills/eval/SKILL.md +237 -0
- package/bizar-dash/src/server/api.mjs +28 -0
- package/bizar-dash/src/server/auth.mjs +155 -1
- package/bizar-dash/src/server/eval-store.mjs +226 -0
- package/bizar-dash/src/server/eval.mjs +347 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +109 -0
- package/bizar-dash/src/server/memory-store.mjs +121 -0
- package/bizar-dash/src/server/ocr.mjs +55 -0
- package/bizar-dash/src/server/otel.mjs +133 -0
- package/bizar-dash/src/server/plugins/registry.mjs +363 -0
- package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
- package/bizar-dash/src/server/plugins/store.mjs +659 -0
- package/bizar-dash/src/server/routes/chat.mjs +246 -170
- package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
- package/bizar-dash/src/server/routes/eval.mjs +147 -0
- package/bizar-dash/src/server/routes/memory.mjs +46 -0
- package/bizar-dash/src/server/routes/ocr.mjs +182 -0
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +82 -48
- package/bizar-dash/src/server/routes/plugins.mjs +220 -0
- package/bizar-dash/src/server/routes/users.mjs +84 -0
- package/bizar-dash/src/server/routes/voice.mjs +131 -0
- package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
- package/bizar-dash/src/server/server.mjs +40 -0
- package/bizar-dash/src/server/voice-store.mjs +202 -0
- package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
- package/bizar-dash/src/server/workspaces.mjs +626 -0
- package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
- package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
- package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
- package/bizar-dash/src/web/components/SettingsSearch.tsx +204 -89
- package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
- package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
- package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
- package/bizar-dash/src/web/lib/search.ts +115 -0
- package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +10 -35
- package/bizar-dash/src/web/mobile/views/QrCodePanel.tsx +69 -0
- package/bizar-dash/src/web/styles/memory.css +166 -1
- package/bizar-dash/src/web/styles/settings.css +80 -0
- package/bizar-dash/src/web/views/Memory.tsx +22 -2
- package/bizar-dash/src/web/views/Settings.tsx +99 -0
- package/bizar-dash/src/web/views/Workspace.tsx +294 -0
- package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphLegend.tsx +29 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphPanel.tsx +192 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphView.tsx +336 -0
- package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
- package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
- package/bizar-dash/tests/bundle-analysis.test.mjs +73 -0
- package/bizar-dash/tests/clipboard.test.mjs +147 -0
- package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
- package/bizar-dash/tests/components/settings-search.test.tsx +180 -0
- package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
- package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
- package/bizar-dash/tests/docker-build.test.mjs +96 -0
- package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
- package/bizar-dash/tests/eval/report.test.mjs +284 -0
- package/bizar-dash/tests/eval/runner.test.mjs +471 -0
- package/bizar-dash/tests/lib/search-fuzzy.test.ts +149 -0
- package/bizar-dash/tests/memory-graph-view.test.tsx +69 -0
- package/bizar-dash/tests/memory-graph.test.mjs +95 -0
- package/bizar-dash/tests/ocr.test.mjs +87 -0
- package/bizar-dash/tests/otel.test.mjs +188 -0
- package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
- package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
- package/bizar-dash/tests/plugins-store.test.mjs +455 -0
- package/bizar-dash/tests/users.test.mjs +108 -0
- package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
- package/bizar-dash/tests/voice-store.test.mjs +148 -0
- package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
- package/bizar-dash/tests/workspaces.test.mjs +527 -0
- package/cli/bin.mjs +72 -2
- package/cli/commands/clip.mjs +146 -0
- package/cli/commands/dash.mjs +6 -0
- package/cli/commands/deploy/cloudflare.mjs +250 -0
- package/cli/commands/deploy/docker.mjs +221 -0
- package/cli/commands/deploy/fly.mjs +161 -0
- package/cli/commands/deploy/vercel.mjs +225 -0
- package/cli/commands/deploy.mjs +240 -0
- package/cli/commands/eval.mjs +378 -0
- package/cli/commands/marketplace.mjs +64 -0
- package/cli/commands/ocr.mjs +165 -0
- package/cli/commands/plugin.mjs +358 -0
- package/cli/commands/voice.mjs +211 -0
- package/cli/commands/workspace.mjs +247 -0
- package/package.json +12 -2
- package/templates/deploy/cloudflare/README.md +32 -0
- package/templates/deploy/cloudflare/functions-index.template.js +15 -0
- package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
- package/templates/deploy/docker/.env.template +16 -0
- package/templates/deploy/docker/README.md +58 -0
- package/templates/deploy/docker/docker-compose.template.yml +23 -0
- package/templates/deploy/fly/README.md +35 -0
- package/templates/deploy/fly/fly.toml.template +28 -0
- package/templates/deploy/vercel/README.md +29 -0
- package/templates/deploy/vercel/api-index.template.js +18 -0
- package/templates/deploy/vercel/vercel.json.template +16 -0
- package/templates/eval-fixtures/README.md +58 -0
- package/templates/eval-fixtures/code-search-basic.json +28 -0
- package/templates/eval-fixtures/latency-bounds.json +16 -0
- package/templates/eval-fixtures/regression-suite.json +79 -0
- package/templates/eval-fixtures/response-format.json +30 -0
- package/templates/eval-fixtures/tool-call-correctness.json +24 -0
- package/templates/plugin-template/README.md +121 -0
- package/templates/plugin-template/index.js +66 -0
- package/templates/plugin-template/plugin.json +42 -0
- package/templates/plugin-template/tests/plugin.test.js +83 -0
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js +0 -361
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js +0 -351
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-Chvf9u_B.js +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/memory-graph.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Tests for getObsidianLinkGraph in memory-store.mjs
|
|
5
|
+
* and getLightRAGGraph in memory-lightrag.mjs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const TEST_STORE = await import('../src/server/memory-store.mjs').then((m) => m);
|
|
15
|
+
|
|
16
|
+
describe('getObsidianLinkGraph', () => {
|
|
17
|
+
let projectRoot;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
projectRoot = join(tmpdir(), `bizar-graph-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
21
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
22
|
+
mkdirSync(join(projectRoot, '.bizar'), { recursive: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
try { rmSync(projectRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function writeNote(relPath, body) {
|
|
30
|
+
const full = join(projectRoot, '.obsidian', relPath);
|
|
31
|
+
mkdirSync(join(projectRoot, '.obsidian', relPath.split('/').slice(0, -1).join('/') || '.'), { recursive: true });
|
|
32
|
+
const title = relPath.split('/').pop()?.replace(/\.md$/i, '') || relPath;
|
|
33
|
+
writeFileSync(full, `---\ntitle: ${title}\n---\n\n${body}`, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('extracts wikilinks and builds nodes + edges', () => {
|
|
37
|
+
writeNote('notes/alpha.md', 'This links to [[Beta Note]] and [[Gamma]].');
|
|
38
|
+
writeNote('notes/beta.md', 'Back to [[Alpha]].');
|
|
39
|
+
writeNote('notes/gamma.md', 'No links here.');
|
|
40
|
+
|
|
41
|
+
const { nodes, edges } = TEST_STORE.getObsidianLinkGraph({ projectRoot, limit: 200 });
|
|
42
|
+
|
|
43
|
+
// Should have 3 nodes.
|
|
44
|
+
assert.ok(nodes.length >= 3, `Expected >= 3 nodes, got ${nodes.length}`);
|
|
45
|
+
|
|
46
|
+
// Each node should have required fields.
|
|
47
|
+
for (const n of nodes) {
|
|
48
|
+
assert.ok('id' in n, 'node must have id');
|
|
49
|
+
assert.ok('label' in n, 'node must have label');
|
|
50
|
+
assert.ok('type' in n, 'node must have type');
|
|
51
|
+
assert.ok('size' in n, 'node must have size');
|
|
52
|
+
assert.ok('group' in n, 'node must have group');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Should have edges.
|
|
56
|
+
assert.ok(edges.length > 0, 'Expected at least 1 edge');
|
|
57
|
+
for (const e of edges) {
|
|
58
|
+
assert.ok('source' in e, 'edge must have source');
|
|
59
|
+
assert.ok('target' in e, 'edge must have target');
|
|
60
|
+
assert.ok('type' in e, 'edge must have type');
|
|
61
|
+
assert.ok('weight' in e, 'edge must have weight');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles empty vault gracefully', () => {
|
|
66
|
+
const { nodes, edges } = TEST_STORE.getObsidianLinkGraph({ projectRoot, limit: 200 });
|
|
67
|
+
assert.ok(Array.isArray(nodes), 'nodes must be an array');
|
|
68
|
+
assert.ok(Array.isArray(edges), 'edges must be an array');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('resolves wikilink targets by basename', () => {
|
|
72
|
+
writeNote('foo.md', 'See [[Bar]] for details.');
|
|
73
|
+
writeNote('bar.md', 'Related to [[Foo]].');
|
|
74
|
+
const { edges } = TEST_STORE.getObsidianLinkGraph({ projectRoot, limit: 200 });
|
|
75
|
+
// Should have resolved [[Bar]] -> bar.md and [[Foo]] -> foo.md.
|
|
76
|
+
assert.ok(edges.length >= 1, 'Expected at least 1 edge from wikilink resolution');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('respects the limit parameter', () => {
|
|
80
|
+
for (let i = 0; i < 10; i++) {
|
|
81
|
+
writeNote(`note${i}.md`, i === 0 ? 'Links to [[Note1]].' : '');
|
|
82
|
+
}
|
|
83
|
+
const { nodes, edges } = TEST_STORE.getObsidianLinkGraph({ projectRoot, limit: 3 });
|
|
84
|
+
assert.ok(nodes.length <= 3, `Expected <= 3 nodes, got ${nodes.length}`);
|
|
85
|
+
assert.ok(edges.length <= 9, `Expected <= 9 edges, got ${edges.length}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('node label uses title frontmatter when available', () => {
|
|
89
|
+
writeNote('mydoc.md', 'Content.');
|
|
90
|
+
const { nodes } = TEST_STORE.getObsidianLinkGraph({ projectRoot, limit: 200 });
|
|
91
|
+
const mydoc = nodes.find((n) => n.id === 'mydoc.md');
|
|
92
|
+
assert.ok(mydoc, 'mydoc.md should be a node');
|
|
93
|
+
assert.strictEqual(mydoc.label, 'mydoc');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/ocr.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* v5.0.0 — Tests for the OCR helper module and routes.
|
|
5
|
+
*
|
|
6
|
+
* The extractText tests require tesseract.js to be installed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, beforeEach, afterEach, before } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
// ── Test OCR helper ────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
describe('ocr.extractText', () => {
|
|
18
|
+
let ocrModule;
|
|
19
|
+
|
|
20
|
+
before(async () => {
|
|
21
|
+
try {
|
|
22
|
+
ocrModule = await import('../src/server/ocr.mjs');
|
|
23
|
+
} catch {
|
|
24
|
+
// tesseract.js or its deps not available
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('extractText returns a string for a valid PNG buffer', { skip: !ocrModule }, async () => {
|
|
29
|
+
const pngBuffer = Buffer.from(
|
|
30
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
31
|
+
'base64',
|
|
32
|
+
);
|
|
33
|
+
const text = await ocrModule.extractText(pngBuffer);
|
|
34
|
+
assert.strictEqual(typeof text, 'string');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('checkOcrHealth returns ok or error message', { skip: !ocrModule }, async () => {
|
|
38
|
+
const health = await ocrModule.checkOcrHealth();
|
|
39
|
+
assert.ok(typeof health.ok === 'boolean');
|
|
40
|
+
assert.ok(typeof health.message === 'string');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ── Test OCR routes ────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
describe('ocr routes', () => {
|
|
47
|
+
let projectRoot;
|
|
48
|
+
let server;
|
|
49
|
+
let baseUrl;
|
|
50
|
+
let ocrRouterModule;
|
|
51
|
+
|
|
52
|
+
before(async () => {
|
|
53
|
+
ocrRouterModule = await import('../src/server/routes/ocr.mjs');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
beforeEach(async () => {
|
|
57
|
+
projectRoot = join(tmpdir(), `bizar-ocr-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
58
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
59
|
+
mkdirSync(join(projectRoot, '.bizar'), { recursive: true });
|
|
60
|
+
|
|
61
|
+
const express = (await import('express')).default;
|
|
62
|
+
const router = await ocrRouterModule.createOcrRouter({ projectRoot });
|
|
63
|
+
const app = express();
|
|
64
|
+
app.use(express.json({ limit: '25mb' }));
|
|
65
|
+
app.use('/api', router);
|
|
66
|
+
|
|
67
|
+
await new Promise((resolve) => {
|
|
68
|
+
server = app.listen(0, () => {
|
|
69
|
+
const addr = server.address();
|
|
70
|
+
baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
71
|
+
resolve();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
try { server?.close?.(); } catch { /* ignore */ }
|
|
78
|
+
try { rmSync(projectRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('GET /api/ocr/list returns entries array', async () => {
|
|
82
|
+
const r = await fetch(`${baseUrl}/api/ocr/list`);
|
|
83
|
+
assert.strictEqual(r.status, 200);
|
|
84
|
+
const data = await r.json();
|
|
85
|
+
assert.ok(Array.isArray(data.entries));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/otel.test.mjs — v4.9.0
|
|
3
|
+
*
|
|
4
|
+
* Tests for src/server/otel.mjs. The OpenTelemetry SDK and helpers
|
|
5
|
+
* must:
|
|
6
|
+
* - export a `tracer` that has `startActiveSpan` (works even before
|
|
7
|
+
* `initOtel()` is called, because the no-op global tracer covers
|
|
8
|
+
* the uninitialised case)
|
|
9
|
+
* - return an SDK instance from `initOtel()` and only initialise
|
|
10
|
+
* once across repeated calls
|
|
11
|
+
* - shut down cleanly via `shutdownOtel()`, even when the OTLP
|
|
12
|
+
* endpoint is unreachable
|
|
13
|
+
* - tolerate a malformed endpoint / thrown init error without
|
|
14
|
+
* crashing the importing module
|
|
15
|
+
*
|
|
16
|
+
* Each test loads the module via a fresh URL to defeat Node's ESM
|
|
17
|
+
* module cache; `otel.mjs` uses module-scoped state (the `sdk` and
|
|
18
|
+
* `shuttingDown` lets) and we want each test to see its own clean
|
|
19
|
+
* registry the same way `metrics.test.mjs` does.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import test from 'node:test';
|
|
23
|
+
import assert from 'node:assert/strict';
|
|
24
|
+
|
|
25
|
+
const OTEL_URL = '../src/server/otel.mjs';
|
|
26
|
+
|
|
27
|
+
async function loadOtel() {
|
|
28
|
+
const url = `${OTEL_URL}?v=${Date.now()}-${Math.random()}`;
|
|
29
|
+
return import(url);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('tracer is exported and has startActiveSpan', async () => {
|
|
33
|
+
const otel = await loadOtel();
|
|
34
|
+
try {
|
|
35
|
+
assert.equal(typeof otel.tracer, 'object');
|
|
36
|
+
assert.equal(typeof otel.tracer.startActiveSpan, 'function');
|
|
37
|
+
} finally {
|
|
38
|
+
await otel.shutdownOtel();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('initOtel() returns an SDK instance and is idempotent', async () => {
|
|
43
|
+
const otel = await loadOtel();
|
|
44
|
+
try {
|
|
45
|
+
// Before init: isOtelEnabled() is false (fresh module load).
|
|
46
|
+
assert.equal(otel.isOtelEnabled(), false);
|
|
47
|
+
const sdk1 = otel.initOtel({
|
|
48
|
+
// Use an endpoint that won't connect; the test only cares
|
|
49
|
+
// that the SDK object is returned, not that spans export.
|
|
50
|
+
endpoint: 'http://127.0.0.1:1/v1/traces',
|
|
51
|
+
});
|
|
52
|
+
assert.ok(sdk1, 'initOtel should return a non-null SDK instance');
|
|
53
|
+
assert.equal(otel.isOtelEnabled(), true);
|
|
54
|
+
|
|
55
|
+
const sdk2 = otel.initOtel({ endpoint: 'http://127.0.0.1:1/v1/traces' });
|
|
56
|
+
assert.equal(
|
|
57
|
+
sdk1,
|
|
58
|
+
sdk2,
|
|
59
|
+
'a second initOtel() call must return the cached SDK instance',
|
|
60
|
+
);
|
|
61
|
+
} finally {
|
|
62
|
+
await otel.shutdownOtel();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('shutdownOtel() is callable and resets the SDK cache', async () => {
|
|
67
|
+
const otel = await loadOtel();
|
|
68
|
+
try {
|
|
69
|
+
otel.initOtel({ endpoint: 'http://127.0.0.1:1/v1/traces' });
|
|
70
|
+
assert.equal(otel.isOtelEnabled(), true);
|
|
71
|
+
await otel.shutdownOtel();
|
|
72
|
+
assert.equal(otel.isOtelEnabled(), false);
|
|
73
|
+
|
|
74
|
+
// Calling shutdownOtel again must be a no-op (does not throw).
|
|
75
|
+
await otel.shutdownOtel();
|
|
76
|
+
assert.equal(otel.isOtelEnabled(), false);
|
|
77
|
+
} finally {
|
|
78
|
+
await otel.shutdownOtel();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('graceful degradation when OTLP endpoint is unreachable', async () => {
|
|
83
|
+
// Port 1 on loopback never accepts connections — the exporter will
|
|
84
|
+
// log batch-send errors internally but must not crash the module.
|
|
85
|
+
const otel = await loadOtel();
|
|
86
|
+
try {
|
|
87
|
+
const sdk = otel.initOtel({
|
|
88
|
+
endpoint: 'http://127.0.0.1:1/v1/traces',
|
|
89
|
+
});
|
|
90
|
+
assert.ok(sdk, 'initOtel must succeed even with an unreachable endpoint');
|
|
91
|
+
assert.equal(otel.isOtelEnabled(), true);
|
|
92
|
+
|
|
93
|
+
// Spans created on the live tracer must complete without errors.
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
otel.tracer.startActiveSpan('test.unreachable_endpoint', async (span) => {
|
|
96
|
+
try {
|
|
97
|
+
span.setAttribute('test.attr', 1);
|
|
98
|
+
span.setStatus({ code: 1 /* OK */ });
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
resolve();
|
|
101
|
+
} catch (e) {
|
|
102
|
+
reject(e);
|
|
103
|
+
} finally {
|
|
104
|
+
span.end();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await otel.shutdownOtel();
|
|
110
|
+
} finally {
|
|
111
|
+
await otel.shutdownOtel();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('spans can be created and ended without errors (no SDK initialised)', async () => {
|
|
116
|
+
// No initOtel call — exercises the no-op tracer path. Route
|
|
117
|
+
// handlers rely on this when OTEL is disabled (the default).
|
|
118
|
+
const otel = await loadOtel();
|
|
119
|
+
assert.equal(otel.isOtelEnabled(), false);
|
|
120
|
+
|
|
121
|
+
const result = await new Promise((resolve, reject) => {
|
|
122
|
+
otel.tracer.startActiveSpan('test.noop', (span) => {
|
|
123
|
+
try {
|
|
124
|
+
// Sanity-check that the no-op span still answers every
|
|
125
|
+
// method without throwing — that's the contract route
|
|
126
|
+
// handlers rely on.
|
|
127
|
+
span.setAttribute('a', 1);
|
|
128
|
+
span.setAttribute('b', 'hello');
|
|
129
|
+
span.addEvent('event-1');
|
|
130
|
+
span.setStatus({ code: 1 });
|
|
131
|
+
resolve('ok');
|
|
132
|
+
} catch (err) {
|
|
133
|
+
reject(err);
|
|
134
|
+
} finally {
|
|
135
|
+
span.end();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
assert.equal(result, 'ok');
|
|
140
|
+
|
|
141
|
+
await otel.shutdownOtel();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('tracer.startActiveSpan callback can be async (returns a Promise)', async () => {
|
|
145
|
+
// The OpenTelemetry spec lets the callback return a Promise;
|
|
146
|
+
// the tracer awaits it. Make sure the no-op implementation
|
|
147
|
+
// (used when the SDK is disabled) honors this.
|
|
148
|
+
const otel = await loadOtel();
|
|
149
|
+
try {
|
|
150
|
+
let observed = null;
|
|
151
|
+
await otel.tracer.startActiveSpan('test.async', async (span) => {
|
|
152
|
+
try {
|
|
153
|
+
observed = await Promise.resolve('inside-span');
|
|
154
|
+
} finally {
|
|
155
|
+
span.end();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
assert.equal(observed, 'inside-span');
|
|
159
|
+
} finally {
|
|
160
|
+
await otel.shutdownOtel();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('initOtel accepts custom serviceName override', async () => {
|
|
165
|
+
const otel = await loadOtel();
|
|
166
|
+
try {
|
|
167
|
+
const sdk = otel.initOtel({
|
|
168
|
+
serviceName: 'bizar-dash-test',
|
|
169
|
+
endpoint: 'http://127.0.0.1:1/v1/traces',
|
|
170
|
+
});
|
|
171
|
+
assert.ok(sdk, 'initOtel with a custom serviceName should succeed');
|
|
172
|
+
await otel.shutdownOtel();
|
|
173
|
+
} finally {
|
|
174
|
+
await otel.shutdownOtel();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('module does not crash when imported (no init, no shutdown)', async () => {
|
|
179
|
+
// Defensive sanity check — every other test file in this repo
|
|
180
|
+
// transitively imports dozens of modules. If a syntax/import
|
|
181
|
+
// error sneaks into otel.mjs, this test catches it before the
|
|
182
|
+
// larger suite blows up somewhere unrelated.
|
|
183
|
+
const otel = await loadOtel();
|
|
184
|
+
assert.equal(typeof otel.initOtel, 'function');
|
|
185
|
+
assert.equal(typeof otel.shutdownOtel, 'function');
|
|
186
|
+
assert.equal(typeof otel.tracer, 'object');
|
|
187
|
+
await otel.shutdownOtel();
|
|
188
|
+
});
|