godcrm/backend/services/__tests__/WidgetService.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

863 lines
28 KiB
JavaScript

/**
* WidgetService Tests - TDD Approach
* Phase 1.2: Testing widget CRUD operations
*
* 🔴 RED → 🟢 GREEN → 🔵 REFACTOR
*/
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import {
createWidget,
getWidgetById,
getWidgetsByDashboard,
updateWidget,
updateWidgetCode,
deleteWidget,
getWidgetData
} from '../WidgetService.js';
import { createModule } from '../ModuleService.js';
import { dbAll, dbGet, dbRun, destroyAdapter, resetAdapter, toBool } from '../../database/connection.js';
// Helper function to create test user
async function createTestUser() {
const uniqueEmail = `test-widgetservice-${Date.now()}@hltrn.cc`;
const result = await dbRun(
'INSERT INTO users (email, password_hash, name, encryption_key_encrypted, email_verified) VALUES (?, ?, ?, ?, ?)',
[uniqueEmail, 'hash', 'Test User', 'encrypted_key', 1]
);
return result.lastInsertRowid;
}
// Helper function to create test space
async function createTestSpace(ownerId) {
const result = await dbRun(
'INSERT INTO spaces (owner_id, name, type) VALUES (?, ?, ?)',
[ownerId, 'Test Space', 'business']
);
return result.lastInsertRowid;
}
// Helper function to create test project
async function createTestProject(ownerId, spaceId) {
const result = await dbRun(
'INSERT INTO projects (owner_id, space_id, name, type) VALUES (?, ?, ?, ?)',
[ownerId, spaceId, 'Test Project', 'business']
);
return result.lastInsertRowid;
}
// Helper function to create test dashboard
async function createTestDashboard(projectId) {
const result = await dbRun(
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
[projectId, 'Test Dashboard', toBool(true)]
);
return result.lastInsertRowid;
}
// Helper function to create test table
async function createTestTable(projectId) {
const result = await dbRun(
'INSERT INTO universal_tables (project_id, name) VALUES (?, ?)',
[projectId, 'Test Table']
);
return result.lastInsertRowid;
}
describe('WidgetService - TDD', () => {
let userId;
let spaceId;
let projectId;
let dashboardId;
beforeEach(async () => {
process.env.TEST_MODE = 'true';
process.env.SKIP_DEV_USER = 'true';
await resetAdapter();
// Create test data
userId = await createTestUser();
spaceId = await createTestSpace(userId);
projectId = await createTestProject(userId, spaceId);
dashboardId = await createTestDashboard(projectId);
});
afterEach(async () => {
await destroyAdapter();
});
// ============================================================
// 🔴 RED PHASE: createWidget (preset)
// ============================================================
describe('createWidget (preset)', () => {
test('should create preset widget with valid data', async () => {
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Active Clients',
description: 'View all active clients',
icon: '📊',
config: { table_id: 1, filters: [] },
position: { x: 0, y: 0, w: 6, h: 4 }
};
const widget = await createWidget(widgetData);
expect(widget).toBeDefined();
expect(widget.id).toBeDefined();
expect(widget.widget_type).toBe('preset');
expect(widget.preset_name).toBe('table_view');
expect(widget.title).toBe('Active Clients');
expect(widget.code).toBeNull();
expect(widget.code_version).toBe(1);
});
test('should reject preset widget without preset_name', async () => {
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'preset',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
};
await expect(createWidget(widgetData)).rejects.toThrow('preset_name is required for preset widgets');
});
test('should reject preset widget with code', async () => {
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
code: 'const invalid = true;',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
};
await expect(createWidget(widgetData)).rejects.toThrow('preset widgets cannot have code');
});
test('should reject widget with non-existent dashboard', async () => {
const widgetData = {
dashboard_id: 99999,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
};
await expect(createWidget(widgetData)).rejects.toThrow('Dashboard not found');
});
});
// ============================================================
// 🔴 RED PHASE: createWidget (custom)
// ============================================================
describe('createWidget (custom)', () => {
test('should create custom widget with code', async () => {
const code = `
export default function CustomWidget({ data }) {
return <div>{data.length} items</div>;
}
`;
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'custom',
code,
title: 'Custom Sales Chart',
description: 'Custom chart visualization',
config: {},
position: { x: 6, y: 0, w: 6, h: 4 }
};
const widget = await createWidget(widgetData);
expect(widget).toBeDefined();
expect(widget.widget_type).toBe('custom');
expect(widget.code).toBe(code);
expect(widget.preset_name).toBeNull();
expect(widget.code_version).toBe(1);
});
test('should reject custom widget without code', async () => {
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'custom',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
};
await expect(createWidget(widgetData)).rejects.toThrow('code is required for custom widgets');
});
test('should reject custom widget with preset_name', async () => {
const widgetData = {
dashboard_id: dashboardId,
widget_type: 'custom',
preset_name: 'table_view',
code: 'const code = true;',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
};
await expect(createWidget(widgetData)).rejects.toThrow('custom widgets cannot have preset_name');
});
});
// ============================================================
// 🔴 RED PHASE: getWidgetById
// ============================================================
describe('getWidgetById', () => {
test('should get existing widget', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const widget = await getWidgetById(created.id);
expect(widget).toBeDefined();
expect(widget.id).toBe(created.id);
expect(widget.title).toBe('Test Widget');
});
test('should return null for non-existent widget', async () => {
const widget = await getWidgetById(99999);
expect(widget).toBeNull();
});
test('should parse JSON fields correctly', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test Widget',
config: { table_id: 5, filters: [{ column: 'status', value: 'active' }] },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const widget = await getWidgetById(created.id);
expect(widget.config).toEqual({ table_id: 5, filters: [{ column: 'status', value: 'active' }] });
expect(widget.position).toEqual({ x: 0, y: 0, w: 6, h: 4 });
});
});
// ============================================================
// 🔴 RED PHASE: getWidgetsByDashboard
// ============================================================
describe('getWidgetsByDashboard', () => {
test('should get all widgets for dashboard', async () => {
await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Widget 1',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'project_stats',
title: 'Widget 2',
config: {},
position: { x: 6, y: 0, w: 6, h: 4 }
});
const widgets = await getWidgetsByDashboard(dashboardId);
expect(widgets).toHaveLength(2);
expect(widgets[0].title).toBe('Widget 1');
expect(widgets[1].title).toBe('Widget 2');
});
test('should return empty array for dashboard with no widgets', async () => {
const widgets = await getWidgetsByDashboard(dashboardId);
expect(widgets).toEqual([]);
});
test('should sort widgets by order_index', async () => {
await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Widget 2',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 },
order_index: 2
});
await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Widget 1',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 },
order_index: 1
});
const widgets = await getWidgetsByDashboard(dashboardId);
expect(widgets[0].title).toBe('Widget 1');
expect(widgets[1].title).toBe('Widget 2');
});
});
// ============================================================
// 🔴 RED PHASE: updateWidget
// ============================================================
describe('updateWidget', () => {
test('should update widget title and description', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Old Title',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const updated = await updateWidget(created.id, {
title: 'New Title',
description: 'New description'
});
expect(updated.title).toBe('New Title');
expect(updated.description).toBe('New description');
});
test('should update widget config', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test',
config: { table_id: 1 },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const updated = await updateWidget(created.id, {
config: { table_id: 2, filters: [] }
});
expect(updated.config).toEqual({ table_id: 2, filters: [] });
});
test('should update widget position', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const updated = await updateWidget(created.id, {
position: { x: 6, y: 4, w: 12, h: 6 }
});
expect(updated.position).toEqual({ x: 6, y: 4, w: 12, h: 6 });
});
test('should reject update for non-existent widget', async () => {
await expect(updateWidget(99999, { title: 'New' })).rejects.toThrow('Widget not found');
});
// ADR-0067 Q2 — audit-log detector for documents-widget canonical drift.
// The mutation MUST emit a `widget.config_updated` row whenever `config`
// is in the update payload so daily drift queries can spot regressions
// during the combined DEV+PROD soak (2026-05-17 → 2026-05-24).
describe('audit-log emission (ADR-0067 Q2)', () => {
const tableId = 1708;
test('config update emits widget.config_updated with before/after', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Docs',
config: { registry_table_id: 100008, documents_table_id: 100008 },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const req = {
user: { id: userId },
requestId: 'test-req-config-update',
spaceId,
get: () => 'vitest-ua',
};
await updateWidget(created.id, {
config: { registry_table_id: 100008, documents_table_id: 100008, title_column: 'name' }
}, req);
const rows = await dbAll(
`SELECT user_id, action, entity_type, entity_id, details, request_id, space_id
FROM audit_log
WHERE action = 'widget.config_updated' AND entity_id = ?`,
[String(created.id)]
);
expect(rows).toHaveLength(1);
const row = rows[0];
expect(row.user_id).toBe(userId);
expect(row.entity_type).toBe('widget');
expect(row.entity_id).toBe(String(created.id));
expect(row.request_id).toBe('test-req-config-update');
expect(Number(row.space_id)).toBe(Number(spaceId));
const details = typeof row.details === 'string' ? JSON.parse(row.details) : row.details;
expect(details.preset_name).toBe('documents');
expect(details.before).toEqual({ registry_table_id: 100008, documents_table_id: 100008 });
expect(details.after).toEqual({
registry_table_id: 100008,
documents_table_id: 100008,
title_column: 'name'
});
});
test('non-config update does NOT emit widget.config_updated', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Docs',
config: { registry_table_id: 100008 },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const req = {
user: { id: userId },
requestId: 'test-req-title-only',
spaceId,
get: () => 'vitest-ua',
};
await updateWidget(created.id, { title: 'Renamed', position: { x: 1, y: 1, w: 2, h: 2 } }, req);
const rows = await dbAll(
`SELECT id FROM audit_log
WHERE action = 'widget.config_updated' AND entity_id = ?`,
[String(created.id)]
);
expect(rows).toHaveLength(0);
});
test('canonical-strip regression IS visible in audit details', async () => {
// Simulates stop-condition (a): a Settings save that erases the
// canonical `registry_table_id` while keeping the legacy key. This
// is exactly what the daily drift SQL filters on.
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Docs',
config: { registry_table_id: 100008, documents_table_id: 100008 },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const req = {
user: { id: userId },
requestId: 'test-req-strip',
spaceId,
get: () => 'vitest-ua',
};
// Bad save — canonical stripped.
await updateWidget(created.id, {
config: { documents_table_id: 100008 }
}, req);
const rows = await dbAll(
`SELECT details FROM audit_log
WHERE action = 'widget.config_updated' AND entity_id = ?`,
[String(created.id)]
);
expect(rows).toHaveLength(1);
const details = typeof rows[0].details === 'string' ? JSON.parse(rows[0].details) : rows[0].details;
expect(details.before.registry_table_id).toBe(100008);
expect(details.after.registry_table_id).toBeUndefined();
// Drift query D would catch exactly this transition: before has the
// canonical key, after does not.
});
test('null req is safe (no throw, no audit row)', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Docs',
config: { registry_table_id: 100008 },
position: { x: 0, y: 0, w: 6, h: 4 }
});
// No req at all — must still UPDATE successfully; audit fires with
// null user_id / request_id / space_id (writeAudit tolerates this).
const updated = await updateWidget(created.id, {
config: { registry_table_id: 100009 }
});
expect(updated.config).toEqual({ registry_table_id: 100009 });
const rows = await dbAll(
`SELECT user_id, request_id, space_id FROM audit_log
WHERE action = 'widget.config_updated' AND entity_id = ?`,
[String(created.id)]
);
expect(rows).toHaveLength(1);
expect(rows[0].user_id).toBeNull();
expect(rows[0].request_id).toBeNull();
expect(rows[0].space_id).toBeNull();
});
// ADR-0067 has nothing preset-specific in the audit contract — preset
// is only a filter hint inside `details.preset_name`. Confirm a
// table_view widget also emits the row (so the drift query can scope
// by preset rather than rely on absence).
test('non-documents preset also emits with preset_name in details', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Tickets',
config: { table_id: tableId },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const req = {
user: { id: userId },
requestId: 'test-req-table-view',
spaceId,
get: () => 'vitest-ua',
};
await updateWidget(created.id, { config: { table_id: tableId, filters: [] } }, req);
const rows = await dbAll(
`SELECT details FROM audit_log
WHERE action = 'widget.config_updated' AND entity_id = ?`,
[String(created.id)]
);
expect(rows).toHaveLength(1);
const details = typeof rows[0].details === 'string' ? JSON.parse(rows[0].details) : rows[0].details;
expect(details.preset_name).toBe('table_view');
});
});
});
// ============================================================
// 🔴 RED PHASE: updateWidgetCode
// ============================================================
describe('updateWidgetCode', () => {
test('should update custom widget code and increment version', async () => {
const code1 = 'const version1 = true;';
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'custom',
code: code1,
title: 'Custom Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
expect(created.code_version).toBe(1);
const code2 = 'const version2 = true;';
const updated = await updateWidgetCode(created.id, code2);
expect(updated.code).toBe(code2);
expect(updated.code_version).toBe(2);
});
test('should reject updating code for preset widget', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
await expect(updateWidgetCode(created.id, 'new code')).rejects.toThrow('Can only update code for custom widgets');
});
test('should reject empty code', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'custom',
code: 'const v1 = true;',
title: 'Test',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
await expect(updateWidgetCode(created.id, '')).rejects.toThrow('Code cannot be empty');
});
});
// ============================================================
// 🔴 RED PHASE: deleteWidget
// ============================================================
describe('deleteWidget', () => {
test('should delete widget', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Test',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
await deleteWidget(created.id);
const widget = await getWidgetById(created.id);
expect(widget).toBeNull();
});
test('should cascade delete child widgets (source_widget_id)', async () => {
const parent = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Parent',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const child = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Child',
source_widget_id: parent.id,
config: {},
position: { x: 6, y: 0, w: 6, h: 4 }
});
await deleteWidget(parent.id);
const childWidget = await getWidgetById(child.id);
expect(childWidget).toBeNull();
});
test('should not throw error when deleting non-existent widget', async () => {
await expect(deleteWidget(99999)).resolves.not.toThrow();
});
});
// ============================================================
// 🔴 RED PHASE: getWidgetData
// ============================================================
describe('getWidgetData', () => {
test('should get data for preset widget with table_id', async () => {
const tableId = await createTestTable(projectId);
// Insert test row
await dbRun(
'INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, ?)',
[tableId, 'test_base_id_1', JSON.stringify({ name: 'John Doe', email: 'john@example.com' })]
);
const widget = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Clients',
config: { table_id: tableId },
position: { x: 0, y: 0, w: 6, h: 4 }
});
const data = await getWidgetData(widget.id);
expect(data).toHaveLength(1);
expect(data[0].data).toEqual({ name: 'John Doe', email: 'john@example.com' });
});
test('should return empty array for widget without table_id', async () => {
const widget = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'project_stats',
title: 'Stats',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const data = await getWidgetData(widget.id);
expect(data).toEqual([]);
});
test('should apply filters from config', async () => {
const tableId = await createTestTable(projectId);
await dbRun(
'INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, ?)',
[tableId, 'test_1', JSON.stringify({ name: 'Active', status: 'active' })]
);
await dbRun(
'INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, ?)',
[tableId, 'test_2', JSON.stringify({ name: 'Inactive', status: 'inactive' })]
);
const widget = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Active Only',
config: {
table_id: tableId,
filters: [{ column: 'status', value: 'active' }]
},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const data = await getWidgetData(widget.id);
expect(data).toHaveLength(1);
expect(data[0].data.status).toBe('active');
});
});
// ============================================================
// ADR-065: is_module from LEFT JOIN modules
// ============================================================
describe('ADR-065: is_module via LEFT JOIN modules', () => {
test('getWidgetById should return is_module=false for widget without module', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Dashboard Only',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const widget = await getWidgetById(created.id);
expect(widget.is_module).toBeFalsy();
expect(widget.module_id).toBeNull();
expect(widget.sidebar_order).toBeNull();
expect(widget.access_level).toBeNull();
});
test('getWidgetById should return is_module=true with module metadata', async () => {
const created = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Documents Module',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
await createModule({
widget_id: created.id,
space_id: spaceId,
sidebar_order: 2,
sidebar_icon: 'file',
access_level: 'admin',
is_pinned: true
});
const widget = await getWidgetById(created.id);
expect(widget.is_module).toBeTruthy();
expect(widget.module_id).toBeDefined();
expect(widget.sidebar_order).toBe(2);
expect(widget.sidebar_icon).toBe('file');
expect(widget.access_level).toBe('admin');
expect(widget.is_pinned).toBeTruthy();
});
test('getWidgetsByDashboard should include is_module for all widgets', async () => {
const w1 = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'table_view',
title: 'Dashboard Widget',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const w2 = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Module Widget',
config: {},
position: { x: 6, y: 0, w: 6, h: 4 }
});
await createModule({
widget_id: w2.id,
space_id: spaceId,
sidebar_order: 0
});
const widgets = await getWidgetsByDashboard(dashboardId);
expect(widgets).toHaveLength(2);
const dashboardOnly = widgets.find(w => w.title === 'Dashboard Widget');
const moduleWidget = widgets.find(w => w.title === 'Module Widget');
expect(dashboardOnly.is_module).toBeFalsy();
expect(dashboardOnly.module_id).toBeNull();
expect(moduleWidget.is_module).toBeTruthy();
expect(moduleWidget.module_id).toBeDefined();
});
test('getWidgetsByDashboard should filter by is_module option', async () => {
const w1 = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'project_stats',
title: 'Stats Only',
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
const w2 = await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: 'documents',
title: 'Docs Module',
config: {},
position: { x: 6, y: 0, w: 6, h: 4 }
});
await createModule({
widget_id: w2.id,
space_id: spaceId
});
// Filter: only modules
const modules = await getWidgetsByDashboard(dashboardId, { is_module: true });
expect(modules).toHaveLength(1);
expect(modules[0].title).toBe('Docs Module');
// Filter: only non-modules
const dashOnly = await getWidgetsByDashboard(dashboardId, { is_module: false });
expect(dashOnly).toHaveLength(1);
expect(dashOnly[0].title).toBe('Stats Only');
// No filter: all
const all = await getWidgetsByDashboard(dashboardId);
expect(all).toHaveLength(2);
});
});
});