@littlebearapps/platform-admin-sdk 2.1.0 → 2.2.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/README.md +2 -5
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +121 -3
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
- package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
- package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
- package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
- package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
- package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
- package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
- package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
- package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
- package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
- package/templates/full/dashboard/src/pages/feedback.astro +365 -0
- package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
- package/templates/full/dashboard/src/pages/map.astro +561 -0
- package/templates/full/dashboard/src/pages/revenue.astro +72 -0
- package/templates/full/dashboard/src/pages/tests.astro +431 -0
- package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
- package/templates/full/scripts/ops/verify-account-total.ts +256 -0
- package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
- package/templates/full/tests/integration/r2-archive.test.ts +108 -0
- package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
- package/templates/shared/.github/workflows/validate-controls.yml +27 -0
- package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
- package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
- package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
- package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
- package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
- package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
- package/templates/shared/dashboard/src/components/Toast.astro +170 -0
- package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
- package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
- package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
- package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
- package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
- package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
- package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
- package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
- package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
- package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
- package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
- package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
- package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
- package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
- package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
- package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
- package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
- package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
- package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
- package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
- package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
- package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
- package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
- package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
- package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
- package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
- package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
- package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
- package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
- package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
- package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
- package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
- package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
- package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
- package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
- package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
- package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
- package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
- package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
- package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
- package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
- package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
- package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
- package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
- package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
- package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
- package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
- package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
- package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
- package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
- package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
- package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
- package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
- package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
- package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
- package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
- package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
- package/templates/standard/tests/integration/connectors.test.ts +241 -0
- package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
- package/templates/standard/tests/integration/ingestion.test.ts +211 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification Script: Platform SDK Telemetry Flow
|
|
3
|
+
*
|
|
4
|
+
* This script tests the Platform SDK telemetry flow including:
|
|
5
|
+
* - TelemetryMessage format validation
|
|
6
|
+
* - MetricsAccumulator initialization
|
|
7
|
+
* - Circuit breaker error handling
|
|
8
|
+
* - Feature ID parsing
|
|
9
|
+
* - End-to-end flow simulation
|
|
10
|
+
*
|
|
11
|
+
* Run with: npx tsx scripts/test-telemetry-flow.ts
|
|
12
|
+
*
|
|
13
|
+
* @task SDK pilot integration verification
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
CircuitBreakerError,
|
|
18
|
+
createMetricsAccumulator,
|
|
19
|
+
CIRCUIT_STATUS,
|
|
20
|
+
KV_KEYS,
|
|
21
|
+
type TelemetryMessage,
|
|
22
|
+
type MetricsAccumulator,
|
|
23
|
+
type FeatureMetrics,
|
|
24
|
+
} from '@littlebearapps/platform-consumer-sdk';
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Utility Functions
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
function parseFeatureId(featureId: string): {
|
|
31
|
+
project: string;
|
|
32
|
+
category: string;
|
|
33
|
+
feature: string;
|
|
34
|
+
} {
|
|
35
|
+
const parts = featureId.split(':');
|
|
36
|
+
if (parts.length !== 3) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Invalid featureId format: "${featureId}". Expected "project:category:feature"`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
project: parts[0],
|
|
43
|
+
category: parts[1],
|
|
44
|
+
feature: parts[2],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createTelemetryMessage(featureId: string, metrics: FeatureMetrics): TelemetryMessage {
|
|
49
|
+
const { project, category, feature } = parseFeatureId(featureId);
|
|
50
|
+
return {
|
|
51
|
+
feature_key: featureId,
|
|
52
|
+
project,
|
|
53
|
+
category,
|
|
54
|
+
feature,
|
|
55
|
+
metrics,
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Test Cases: TelemetryMessage Format
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
console.log('='.repeat(70));
|
|
65
|
+
console.log('Platform SDK Telemetry Flow Verification');
|
|
66
|
+
console.log('='.repeat(70));
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
let passed = 0;
|
|
70
|
+
let failed = 0;
|
|
71
|
+
|
|
72
|
+
// Test 1: TelemetryMessage creation
|
|
73
|
+
console.log('Test 1: TelemetryMessage Creation');
|
|
74
|
+
console.log('-'.repeat(50));
|
|
75
|
+
|
|
76
|
+
const testFeatureId = 'scout:ocr:process';
|
|
77
|
+
const testMetrics: FeatureMetrics = {
|
|
78
|
+
d1Reads: 5,
|
|
79
|
+
d1Writes: 2,
|
|
80
|
+
d1RowsRead: 50,
|
|
81
|
+
d1RowsWritten: 10,
|
|
82
|
+
kvReads: 3,
|
|
83
|
+
kvWrites: 1,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const message = createTelemetryMessage(testFeatureId, testMetrics);
|
|
88
|
+
|
|
89
|
+
const checks = [
|
|
90
|
+
{ name: 'feature_key matches', pass: message.feature_key === testFeatureId },
|
|
91
|
+
{ name: 'project parsed correctly', pass: message.project === 'scout' },
|
|
92
|
+
{ name: 'category parsed correctly', pass: message.category === 'ocr' },
|
|
93
|
+
{ name: 'feature parsed correctly', pass: message.feature === 'process' },
|
|
94
|
+
{ name: 'timestamp is number', pass: typeof message.timestamp === 'number' },
|
|
95
|
+
{ name: 'timestamp is recent', pass: Date.now() - message.timestamp < 1000 },
|
|
96
|
+
{ name: 'metrics.d1Reads preserved', pass: message.metrics.d1Reads === 5 },
|
|
97
|
+
{ name: 'metrics.d1Writes preserved', pass: message.metrics.d1Writes === 2 },
|
|
98
|
+
{ name: 'metrics.kvReads preserved', pass: message.metrics.kvReads === 3 },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const check of checks) {
|
|
102
|
+
if (check.pass) {
|
|
103
|
+
console.log(` ✓ ${check.name}`);
|
|
104
|
+
passed++;
|
|
105
|
+
} else {
|
|
106
|
+
console.log(` ✗ ${check.name}`);
|
|
107
|
+
failed++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.log(` ✗ TelemetryMessage creation failed: ${e}`);
|
|
112
|
+
failed++;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log();
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Test Cases: MetricsAccumulator
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
console.log('Test 2: MetricsAccumulator Initialization');
|
|
122
|
+
console.log('-'.repeat(50));
|
|
123
|
+
|
|
124
|
+
const accumulator = createMetricsAccumulator();
|
|
125
|
+
|
|
126
|
+
const accumulatorChecks: Array<{ name: string; field: keyof MetricsAccumulator }> = [
|
|
127
|
+
{ name: 'd1Writes starts at 0', field: 'd1Writes' },
|
|
128
|
+
{ name: 'd1Reads starts at 0', field: 'd1Reads' },
|
|
129
|
+
{ name: 'd1RowsRead starts at 0', field: 'd1RowsRead' },
|
|
130
|
+
{ name: 'd1RowsWritten starts at 0', field: 'd1RowsWritten' },
|
|
131
|
+
{ name: 'kvReads starts at 0', field: 'kvReads' },
|
|
132
|
+
{ name: 'kvWrites starts at 0', field: 'kvWrites' },
|
|
133
|
+
{ name: 'kvDeletes starts at 0', field: 'kvDeletes' },
|
|
134
|
+
{ name: 'kvLists starts at 0', field: 'kvLists' },
|
|
135
|
+
{ name: 'aiRequests starts at 0', field: 'aiRequests' },
|
|
136
|
+
{ name: 'aiNeurons starts at 0', field: 'aiNeurons' },
|
|
137
|
+
{ name: 'vectorizeQueries starts at 0', field: 'vectorizeQueries' },
|
|
138
|
+
{ name: 'vectorizeInserts starts at 0', field: 'vectorizeInserts' },
|
|
139
|
+
// vectorizeDeletes removed - Analytics Engine 20 double limit
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
for (const check of accumulatorChecks) {
|
|
143
|
+
if (accumulator[check.field] === 0) {
|
|
144
|
+
console.log(` ✓ ${check.name}`);
|
|
145
|
+
passed++;
|
|
146
|
+
} else {
|
|
147
|
+
console.log(` ✗ ${check.name} (got ${accumulator[check.field]})`);
|
|
148
|
+
failed++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Test accumulator mutation
|
|
153
|
+
accumulator.d1Writes += 5;
|
|
154
|
+
accumulator.kvReads += 3;
|
|
155
|
+
|
|
156
|
+
if (accumulator.d1Writes === 5 && accumulator.kvReads === 3) {
|
|
157
|
+
console.log(' ✓ Accumulator is mutable');
|
|
158
|
+
passed++;
|
|
159
|
+
} else {
|
|
160
|
+
console.log(' ✗ Accumulator mutation failed');
|
|
161
|
+
failed++;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log();
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Test Cases: CircuitBreakerError
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
console.log('Test 3: CircuitBreakerError');
|
|
171
|
+
console.log('-'.repeat(50));
|
|
172
|
+
|
|
173
|
+
const cbError = new CircuitBreakerError('scout:ocr:process', 'feature', 'Budget exceeded');
|
|
174
|
+
|
|
175
|
+
const errorChecks = [
|
|
176
|
+
{ name: 'is Error instance', pass: cbError instanceof Error },
|
|
177
|
+
{ name: 'is CircuitBreakerError instance', pass: cbError instanceof CircuitBreakerError },
|
|
178
|
+
{ name: 'name is CircuitBreakerError', pass: cbError.name === 'CircuitBreakerError' },
|
|
179
|
+
{ name: 'featureId preserved', pass: cbError.featureId === 'scout:ocr:process' },
|
|
180
|
+
{ name: 'level is feature', pass: cbError.level === 'feature' },
|
|
181
|
+
{ name: 'reason preserved', pass: cbError.reason === 'Budget exceeded' },
|
|
182
|
+
{ name: 'message contains featureId', pass: cbError.message.includes('scout:ocr:process') },
|
|
183
|
+
{ name: 'message contains reason', pass: cbError.message.includes('Budget exceeded') },
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
for (const check of errorChecks) {
|
|
187
|
+
if (check.pass) {
|
|
188
|
+
console.log(` ✓ ${check.name}`);
|
|
189
|
+
passed++;
|
|
190
|
+
} else {
|
|
191
|
+
console.log(` ✗ ${check.name}`);
|
|
192
|
+
failed++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Test error without reason
|
|
197
|
+
const cbErrorNoReason = new CircuitBreakerError('scout:ocr:process', 'project');
|
|
198
|
+
if (cbErrorNoReason.reason === undefined) {
|
|
199
|
+
console.log(' ✓ Error without reason has undefined reason');
|
|
200
|
+
passed++;
|
|
201
|
+
} else {
|
|
202
|
+
console.log(' ✗ Error without reason should have undefined reason');
|
|
203
|
+
failed++;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Test different levels
|
|
207
|
+
const levelTests: Array<{ level: 'feature' | 'project' | 'global' }> = [
|
|
208
|
+
{ level: 'feature' },
|
|
209
|
+
{ level: 'project' },
|
|
210
|
+
{ level: 'global' },
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
for (const test of levelTests) {
|
|
214
|
+
const err = new CircuitBreakerError('test:a:b', test.level);
|
|
215
|
+
if (err.level === test.level) {
|
|
216
|
+
console.log(` ✓ Level ${test.level} works correctly`);
|
|
217
|
+
passed++;
|
|
218
|
+
} else {
|
|
219
|
+
console.log(` ✗ Level ${test.level} failed`);
|
|
220
|
+
failed++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log();
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Test Cases: KV Key Patterns
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
console.log('Test 4: KV Key Patterns');
|
|
231
|
+
console.log('-'.repeat(50));
|
|
232
|
+
|
|
233
|
+
const kvKeyTests = [
|
|
234
|
+
{
|
|
235
|
+
name: 'featureStatus',
|
|
236
|
+
fn: () => KV_KEYS.featureStatus('scout:ocr:process'),
|
|
237
|
+
expected: 'CONFIG:FEATURE:scout:ocr:process:STATUS',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'projectStatus',
|
|
241
|
+
fn: () => KV_KEYS.projectStatus('scout'),
|
|
242
|
+
expected: 'CONFIG:PROJECT:scout:STATUS',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'globalStatus',
|
|
246
|
+
fn: () => KV_KEYS.globalStatus(),
|
|
247
|
+
expected: 'CONFIG:GLOBAL:STATUS',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'featureReason',
|
|
251
|
+
fn: () => KV_KEYS.featureReason('scout:ocr:process'),
|
|
252
|
+
expected: 'CONFIG:FEATURE:scout:ocr:process:REASON',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'featureDisabledAt',
|
|
256
|
+
fn: () => KV_KEYS.featureDisabledAt('scout:ocr:process'),
|
|
257
|
+
expected: 'CONFIG:FEATURE:scout:ocr:process:DISABLED_AT',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'featureAutoResetAt',
|
|
261
|
+
fn: () => KV_KEYS.featureAutoResetAt('scout:ocr:process'),
|
|
262
|
+
expected: 'CONFIG:FEATURE:scout:ocr:process:AUTO_RESET_AT',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'legacy.enabled',
|
|
266
|
+
fn: () => KV_KEYS.legacy.enabled('scout:ocr:process'),
|
|
267
|
+
expected: 'FEATURE:scout:ocr:process:enabled',
|
|
268
|
+
},
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
for (const test of kvKeyTests) {
|
|
272
|
+
const result = test.fn();
|
|
273
|
+
if (result === test.expected) {
|
|
274
|
+
console.log(` ✓ ${test.name}: ${result}`);
|
|
275
|
+
passed++;
|
|
276
|
+
} else {
|
|
277
|
+
console.log(` ✗ ${test.name}: expected "${test.expected}", got "${result}"`);
|
|
278
|
+
failed++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log();
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// Test Cases: Circuit Status Constants
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
console.log('Test 5: Circuit Status Constants');
|
|
289
|
+
console.log('-'.repeat(50));
|
|
290
|
+
|
|
291
|
+
if (CIRCUIT_STATUS.GO === 'GO') {
|
|
292
|
+
console.log(' ✓ CIRCUIT_STATUS.GO = "GO"');
|
|
293
|
+
passed++;
|
|
294
|
+
} else {
|
|
295
|
+
console.log(' ✗ CIRCUIT_STATUS.GO should be "GO"');
|
|
296
|
+
failed++;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (CIRCUIT_STATUS.STOP === 'STOP') {
|
|
300
|
+
console.log(' ✓ CIRCUIT_STATUS.STOP = "STOP"');
|
|
301
|
+
passed++;
|
|
302
|
+
} else {
|
|
303
|
+
console.log(' ✗ CIRCUIT_STATUS.STOP should be "STOP"');
|
|
304
|
+
failed++;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log();
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Test Cases: Feature ID Parsing
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
console.log('Test 6: Feature ID Parsing');
|
|
314
|
+
console.log('-'.repeat(50));
|
|
315
|
+
|
|
316
|
+
const validFeatureIds = [
|
|
317
|
+
{ id: 'scout:ocr:process', project: 'scout', category: 'ocr', feature: 'process' },
|
|
318
|
+
{
|
|
319
|
+
id: 'brand-copilot:scanner:github',
|
|
320
|
+
project: 'brand-copilot',
|
|
321
|
+
category: 'scanner',
|
|
322
|
+
feature: 'github',
|
|
323
|
+
},
|
|
324
|
+
{ id: 'platform:usage:scheduled', project: 'platform', category: 'usage', feature: 'scheduled' },
|
|
325
|
+
{ id: 'a:b:c', project: 'a', category: 'b', feature: 'c' },
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
for (const test of validFeatureIds) {
|
|
329
|
+
try {
|
|
330
|
+
const parsed = parseFeatureId(test.id);
|
|
331
|
+
if (
|
|
332
|
+
parsed.project === test.project &&
|
|
333
|
+
parsed.category === test.category &&
|
|
334
|
+
parsed.feature === test.feature
|
|
335
|
+
) {
|
|
336
|
+
console.log(` ✓ Parsed "${test.id}" correctly`);
|
|
337
|
+
passed++;
|
|
338
|
+
} else {
|
|
339
|
+
console.log(` ✗ Parsed "${test.id}" incorrectly`);
|
|
340
|
+
failed++;
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
console.log(` ✗ Failed to parse valid ID "${test.id}"`);
|
|
344
|
+
failed++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Note: '::' and 'a:b:' technically parse to 3 parts (empty strings are valid parts)
|
|
349
|
+
// The SDK doesn't validate empty parts - only that there are exactly 3 parts
|
|
350
|
+
const invalidFeatureIds = ['scout', 'scout:ocr', 'scout:ocr:process:extra', ''];
|
|
351
|
+
|
|
352
|
+
for (const id of invalidFeatureIds) {
|
|
353
|
+
try {
|
|
354
|
+
parseFeatureId(id);
|
|
355
|
+
console.log(` ✗ Should have rejected invalid ID "${id}"`);
|
|
356
|
+
failed++;
|
|
357
|
+
} catch {
|
|
358
|
+
console.log(` ✓ Correctly rejected invalid ID "${id}"`);
|
|
359
|
+
passed++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log();
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Test Cases: End-to-End Flow Simulation
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
console.log('Test 7: End-to-End Flow Simulation');
|
|
370
|
+
console.log('-'.repeat(50));
|
|
371
|
+
|
|
372
|
+
// Simulate a complete telemetry flow
|
|
373
|
+
const flowAccumulator = createMetricsAccumulator();
|
|
374
|
+
|
|
375
|
+
// Simulate D1 operations
|
|
376
|
+
flowAccumulator.d1Reads += 1;
|
|
377
|
+
flowAccumulator.d1RowsRead += 10;
|
|
378
|
+
flowAccumulator.d1Writes += 2;
|
|
379
|
+
flowAccumulator.d1RowsWritten += 5;
|
|
380
|
+
|
|
381
|
+
// Simulate KV operations
|
|
382
|
+
flowAccumulator.kvReads += 3;
|
|
383
|
+
flowAccumulator.kvWrites += 1;
|
|
384
|
+
|
|
385
|
+
// Simulate AI operations
|
|
386
|
+
flowAccumulator.aiRequests += 1;
|
|
387
|
+
flowAccumulator.aiNeurons += 500;
|
|
388
|
+
|
|
389
|
+
// Convert to telemetry message
|
|
390
|
+
const flowFeatureId = 'platform:usage:fetch';
|
|
391
|
+
const flowMetrics: FeatureMetrics = {
|
|
392
|
+
d1Reads: flowAccumulator.d1Reads,
|
|
393
|
+
d1Writes: flowAccumulator.d1Writes,
|
|
394
|
+
d1RowsRead: flowAccumulator.d1RowsRead,
|
|
395
|
+
d1RowsWritten: flowAccumulator.d1RowsWritten,
|
|
396
|
+
kvReads: flowAccumulator.kvReads,
|
|
397
|
+
kvWrites: flowAccumulator.kvWrites,
|
|
398
|
+
aiRequests: flowAccumulator.aiRequests,
|
|
399
|
+
aiNeurons: flowAccumulator.aiNeurons,
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const flowMessage = createTelemetryMessage(flowFeatureId, flowMetrics);
|
|
403
|
+
|
|
404
|
+
// Validate the message structure for queue consumption
|
|
405
|
+
const messageValidation = [
|
|
406
|
+
{ name: 'Has feature_key', pass: typeof flowMessage.feature_key === 'string' },
|
|
407
|
+
{ name: 'Has project', pass: typeof flowMessage.project === 'string' },
|
|
408
|
+
{ name: 'Has category', pass: typeof flowMessage.category === 'string' },
|
|
409
|
+
{ name: 'Has feature', pass: typeof flowMessage.feature === 'string' },
|
|
410
|
+
{ name: 'Has timestamp', pass: typeof flowMessage.timestamp === 'number' },
|
|
411
|
+
{ name: 'Has metrics object', pass: typeof flowMessage.metrics === 'object' },
|
|
412
|
+
{ name: 'D1 reads tracked', pass: flowMessage.metrics.d1Reads === 1 },
|
|
413
|
+
{ name: 'D1 writes tracked', pass: flowMessage.metrics.d1Writes === 2 },
|
|
414
|
+
{ name: 'D1 rows read tracked', pass: flowMessage.metrics.d1RowsRead === 10 },
|
|
415
|
+
{ name: 'D1 rows written tracked', pass: flowMessage.metrics.d1RowsWritten === 5 },
|
|
416
|
+
{ name: 'KV reads tracked', pass: flowMessage.metrics.kvReads === 3 },
|
|
417
|
+
{ name: 'KV writes tracked', pass: flowMessage.metrics.kvWrites === 1 },
|
|
418
|
+
{ name: 'AI requests tracked', pass: flowMessage.metrics.aiRequests === 1 },
|
|
419
|
+
{ name: 'AI neurons tracked', pass: flowMessage.metrics.aiNeurons === 500 },
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
for (const check of messageValidation) {
|
|
423
|
+
if (check.pass) {
|
|
424
|
+
console.log(` ✓ ${check.name}`);
|
|
425
|
+
passed++;
|
|
426
|
+
} else {
|
|
427
|
+
console.log(` ✗ ${check.name}`);
|
|
428
|
+
failed++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
console.log();
|
|
433
|
+
console.log(' Simulated telemetry message:');
|
|
434
|
+
console.log(
|
|
435
|
+
JSON.stringify(flowMessage, null, 2)
|
|
436
|
+
.split('\n')
|
|
437
|
+
.map((l) => ' ' + l)
|
|
438
|
+
.join('\n')
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
console.log();
|
|
442
|
+
|
|
443
|
+
// ============================================================================
|
|
444
|
+
// Final Summary
|
|
445
|
+
// ============================================================================
|
|
446
|
+
|
|
447
|
+
console.log('='.repeat(70));
|
|
448
|
+
console.log('FINAL SUMMARY');
|
|
449
|
+
console.log('='.repeat(70));
|
|
450
|
+
console.log(`Total tests: ${passed + failed}`);
|
|
451
|
+
console.log(`Passed: ${passed}`);
|
|
452
|
+
console.log(`Failed: ${failed}`);
|
|
453
|
+
console.log();
|
|
454
|
+
|
|
455
|
+
if (failed > 0) {
|
|
456
|
+
console.log('❌ VERIFICATION FAILED');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
} else {
|
|
459
|
+
console.log('✅ ALL VERIFICATIONS PASSED');
|
|
460
|
+
console.log();
|
|
461
|
+
console.log('Platform SDK telemetry flow is working correctly.');
|
|
462
|
+
console.log('Ready for production deployment.');
|
|
463
|
+
process.exit(0);
|
|
464
|
+
}
|