services-as-software 2.1.1 → 2.1.3

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.
@@ -0,0 +1,414 @@
1
+ /**
2
+ * SLA (Service Level Agreement) Tests
3
+ *
4
+ * Tests for defining and monitoring service level agreements.
5
+ */
6
+
7
+ import { describe, it, expect, vi } from 'vitest'
8
+ import {
9
+ defineSLA,
10
+ createSLO,
11
+ monitorSLA,
12
+ checkSLACompliance,
13
+ calculateErrorBudget,
14
+ createSLAAlert,
15
+ recordSLABreach,
16
+ getSLAReport,
17
+ } from '../src/sla/index.js'
18
+
19
+ describe('Service Level Agreements', () => {
20
+ describe('defineSLA', () => {
21
+ it('should define a basic SLA', () => {
22
+ const sla = defineSLA({
23
+ name: 'Standard SLA',
24
+ version: '1.0',
25
+ tier: 'standard',
26
+ uptimeTarget: 99.9,
27
+ responseTimeTarget: 200, // ms
28
+ })
29
+
30
+ expect(sla.name).toBe('Standard SLA')
31
+ expect(sla.tier).toBe('standard')
32
+ expect(sla.uptimeTarget).toBe(99.9)
33
+ expect(sla.responseTimeTarget).toBe(200)
34
+ })
35
+
36
+ it('should define SLA with support levels', () => {
37
+ const sla = defineSLA({
38
+ name: 'Premium SLA',
39
+ tier: 'premium',
40
+ support: {
41
+ hours: '24/7',
42
+ channels: ['email', 'chat', 'phone'],
43
+ responseTime: {
44
+ critical: '15 minutes',
45
+ high: '1 hour',
46
+ medium: '4 hours',
47
+ low: '24 hours',
48
+ },
49
+ },
50
+ })
51
+
52
+ expect(sla.support.hours).toBe('24/7')
53
+ expect(sla.support.channels).toContain('phone')
54
+ expect(sla.support.responseTime.critical).toBe('15 minutes')
55
+ })
56
+
57
+ it('should define SLA with credit policy', () => {
58
+ const sla = defineSLA({
59
+ name: 'Enterprise SLA',
60
+ tier: 'enterprise',
61
+ uptimeTarget: 99.99,
62
+ creditPolicy: {
63
+ '99.99-99.9': 10, // 10% credit for 99.9-99.99% uptime
64
+ '99.9-99.0': 25, // 25% credit
65
+ 'below-99.0': 50, // 50% credit
66
+ },
67
+ maxCredit: 100,
68
+ })
69
+
70
+ expect(sla.creditPolicy['99.99-99.9']).toBe(10)
71
+ expect(sla.maxCredit).toBe(100)
72
+ })
73
+
74
+ it('should define SLA with exclusions', () => {
75
+ const sla = defineSLA({
76
+ name: 'Standard SLA',
77
+ tier: 'standard',
78
+ exclusions: [
79
+ 'Scheduled maintenance',
80
+ 'Force majeure',
81
+ 'Customer-caused issues',
82
+ ],
83
+ maintenanceWindows: [
84
+ { day: 'Sunday', time: '02:00-06:00', timezone: 'UTC' },
85
+ ],
86
+ })
87
+
88
+ expect(sla.exclusions).toContain('Scheduled maintenance')
89
+ expect(sla.maintenanceWindows).toHaveLength(1)
90
+ })
91
+
92
+ it('should define SLA with effective dates', () => {
93
+ const sla = defineSLA({
94
+ name: 'Q1 SLA',
95
+ tier: 'standard',
96
+ effectiveFrom: new Date('2024-01-01'),
97
+ effectiveUntil: new Date('2024-03-31'),
98
+ })
99
+
100
+ expect(sla.effectiveFrom).toEqual(new Date('2024-01-01'))
101
+ expect(sla.effectiveUntil).toEqual(new Date('2024-03-31'))
102
+ })
103
+ })
104
+
105
+ describe('createSLO', () => {
106
+ it('should create an SLO (Service Level Objective)', () => {
107
+ const slo = createSLO({
108
+ name: 'API Availability',
109
+ metric: 'availability',
110
+ target: 99.9,
111
+ comparison: '>=',
112
+ windowType: 'rolling',
113
+ windowDuration: '30d',
114
+ })
115
+
116
+ expect(slo.name).toBe('API Availability')
117
+ expect(slo.metric).toBe('availability')
118
+ expect(slo.target).toBe(99.9)
119
+ expect(slo.windowDuration).toBe('30d')
120
+ })
121
+
122
+ it('should create SLO with error budget', () => {
123
+ const slo = createSLO({
124
+ name: 'Latency P99',
125
+ metric: 'latency',
126
+ target: 200, // 200ms
127
+ unit: 'ms',
128
+ errorBudget: 0.1, // 0.1% error budget
129
+ })
130
+
131
+ expect(slo.errorBudget).toBe(0.1)
132
+ })
133
+
134
+ it('should create SLO with alerting config', () => {
135
+ const slo = createSLO({
136
+ name: 'Error Rate',
137
+ metric: 'error-rate',
138
+ target: 0.1, // 0.1%
139
+ comparison: '<=',
140
+ alertThreshold: 0.08, // Alert at 0.08%
141
+ alertOnBreach: true,
142
+ })
143
+
144
+ expect(slo.alertThreshold).toBe(0.08)
145
+ expect(slo.alertOnBreach).toBe(true)
146
+ })
147
+ })
148
+
149
+ describe('monitorSLA', () => {
150
+ it('should start monitoring an SLA', async () => {
151
+ const sla = defineSLA({
152
+ name: 'Test SLA',
153
+ tier: 'standard',
154
+ uptimeTarget: 99.9,
155
+ })
156
+
157
+ const monitor = await monitorSLA(sla, {
158
+ checkInterval: 60000, // every minute
159
+ metricsSource: async () => ({
160
+ uptime: 99.95,
161
+ responseTime: 150,
162
+ }),
163
+ })
164
+
165
+ expect(monitor.status).toBe('monitoring')
166
+ expect(monitor.slaId).toBe(sla.id)
167
+ })
168
+
169
+ it('should emit events on SLA status change', async () => {
170
+ const onBreach = vi.fn()
171
+
172
+ const sla = defineSLA({
173
+ name: 'Test SLA',
174
+ tier: 'standard',
175
+ uptimeTarget: 99.9,
176
+ })
177
+
178
+ const monitor = await monitorSLA(sla, {
179
+ metricsSource: async () => ({ uptime: 99.0 }), // below target
180
+ onBreach,
181
+ })
182
+
183
+ await monitor.check()
184
+
185
+ expect(onBreach).toHaveBeenCalled()
186
+ })
187
+
188
+ it('should track SLA burn rate', async () => {
189
+ const sla = defineSLA({
190
+ name: 'Test SLA',
191
+ tier: 'standard',
192
+ uptimeTarget: 99.9,
193
+ })
194
+
195
+ const monitor = await monitorSLA(sla, {
196
+ metricsSource: async () => ({ uptime: 99.5 }),
197
+ })
198
+
199
+ const stats = await monitor.getStats()
200
+
201
+ expect(stats.burnRate).toBeDefined()
202
+ expect(stats.errorBudgetRemaining).toBeDefined()
203
+ })
204
+ })
205
+
206
+ describe('checkSLACompliance', () => {
207
+ it('should check if metrics are within SLA', () => {
208
+ const sla = defineSLA({
209
+ name: 'Test SLA',
210
+ tier: 'standard',
211
+ uptimeTarget: 99.9,
212
+ responseTimeTarget: 200,
213
+ })
214
+
215
+ const result = checkSLACompliance(sla, {
216
+ uptime: 99.95,
217
+ responseTime: 150,
218
+ })
219
+
220
+ expect(result.compliant).toBe(true)
221
+ expect(result.metrics.uptime.passed).toBe(true)
222
+ expect(result.metrics.responseTime.passed).toBe(true)
223
+ })
224
+
225
+ it('should detect SLA breach', () => {
226
+ const sla = defineSLA({
227
+ name: 'Test SLA',
228
+ tier: 'standard',
229
+ uptimeTarget: 99.9,
230
+ })
231
+
232
+ const result = checkSLACompliance(sla, {
233
+ uptime: 99.5, // below target
234
+ })
235
+
236
+ expect(result.compliant).toBe(false)
237
+ expect(result.metrics.uptime.passed).toBe(false)
238
+ expect(result.metrics.uptime.deviation).toBe(-0.4)
239
+ })
240
+
241
+ it('should exclude maintenance windows from compliance check', () => {
242
+ const sla = defineSLA({
243
+ name: 'Test SLA',
244
+ tier: 'standard',
245
+ uptimeTarget: 99.9,
246
+ maintenanceWindows: [
247
+ { day: 'Sunday', time: '02:00-06:00', timezone: 'UTC' },
248
+ ],
249
+ })
250
+
251
+ const result = checkSLACompliance(sla, {
252
+ uptime: 99.5,
253
+ maintenanceHours: 4, // 4 hours of maintenance
254
+ })
255
+
256
+ // Should adjust uptime calculation to exclude maintenance
257
+ expect(result.adjustedUptime).toBeGreaterThan(99.5)
258
+ })
259
+ })
260
+
261
+ describe('calculateErrorBudget', () => {
262
+ it('should calculate remaining error budget', () => {
263
+ const budget = calculateErrorBudget({
264
+ target: 99.9, // 99.9% uptime
265
+ windowDays: 30,
266
+ currentUptime: 99.95,
267
+ })
268
+
269
+ expect(budget.totalBudget).toBe(43.2) // 0.1% of 30 days in minutes
270
+ expect(budget.consumed).toBeLessThan(budget.totalBudget)
271
+ expect(budget.remaining).toBeGreaterThan(0)
272
+ expect(budget.remainingPercentage).toBeGreaterThan(0)
273
+ })
274
+
275
+ it('should indicate when error budget is exhausted', () => {
276
+ const budget = calculateErrorBudget({
277
+ target: 99.9,
278
+ windowDays: 30,
279
+ currentUptime: 99.0, // significantly below target
280
+ })
281
+
282
+ expect(budget.remaining).toBeLessThanOrEqual(0)
283
+ expect(budget.exhausted).toBe(true)
284
+ })
285
+
286
+ it('should calculate burn rate', () => {
287
+ const budget = calculateErrorBudget({
288
+ target: 99.9,
289
+ windowDays: 30,
290
+ currentUptime: 99.85,
291
+ daysPassed: 15,
292
+ })
293
+
294
+ // Burn rate > 1 means consuming budget faster than allowed
295
+ expect(budget.burnRate).toBeDefined()
296
+ })
297
+ })
298
+
299
+ describe('createSLAAlert', () => {
300
+ it('should create alert configuration', () => {
301
+ const alert = createSLAAlert({
302
+ name: 'High Error Rate Alert',
303
+ condition: 'error_rate > 0.1',
304
+ severity: 'critical',
305
+ channels: ['email', 'slack', 'pagerduty'],
306
+ cooldown: 300, // 5 minutes
307
+ })
308
+
309
+ expect(alert.name).toBe('High Error Rate Alert')
310
+ expect(alert.severity).toBe('critical')
311
+ expect(alert.channels).toContain('pagerduty')
312
+ })
313
+
314
+ it('should support escalation paths', () => {
315
+ const alert = createSLAAlert({
316
+ name: 'SLA Breach Alert',
317
+ condition: 'uptime < 99.9',
318
+ escalation: [
319
+ { after: '0m', notify: 'on-call-engineer' },
320
+ { after: '15m', notify: 'engineering-manager' },
321
+ { after: '30m', notify: 'vp-engineering' },
322
+ ],
323
+ })
324
+
325
+ expect(alert.escalation).toHaveLength(3)
326
+ expect(alert.escalation[1].after).toBe('15m')
327
+ })
328
+ })
329
+
330
+ describe('recordSLABreach', () => {
331
+ it('should record an SLA breach incident', async () => {
332
+ const sla = defineSLA({
333
+ name: 'Test SLA',
334
+ tier: 'standard',
335
+ uptimeTarget: 99.9,
336
+ })
337
+
338
+ const breach = await recordSLABreach({
339
+ slaId: sla.id,
340
+ metric: 'uptime',
341
+ expected: 99.9,
342
+ actual: 99.5,
343
+ startedAt: new Date('2024-01-15T10:00:00Z'),
344
+ endedAt: new Date('2024-01-15T12:00:00Z'),
345
+ impact: 'partial',
346
+ affectedCustomers: 150,
347
+ })
348
+
349
+ expect(breach.id).toBeDefined()
350
+ expect(breach.duration).toBe(7200000) // 2 hours in ms
351
+ expect(breach.affectedCustomers).toBe(150)
352
+ })
353
+
354
+ it('should calculate credit owed', async () => {
355
+ const sla = defineSLA({
356
+ name: 'Test SLA',
357
+ tier: 'enterprise',
358
+ uptimeTarget: 99.99,
359
+ creditPolicy: {
360
+ '99.99-99.9': 10,
361
+ '99.9-99.0': 25,
362
+ },
363
+ })
364
+
365
+ const breach = await recordSLABreach({
366
+ slaId: sla.id,
367
+ metric: 'uptime',
368
+ actual: 99.5,
369
+ monthlyBillingAmount: 10000,
370
+ })
371
+
372
+ expect(breach.creditOwed).toBe(2500) // 25% of $10,000
373
+ })
374
+ })
375
+
376
+ describe('getSLAReport', () => {
377
+ it('should generate SLA compliance report', async () => {
378
+ const sla = defineSLA({
379
+ name: 'Test SLA',
380
+ tier: 'standard',
381
+ uptimeTarget: 99.9,
382
+ })
383
+
384
+ const report = await getSLAReport({
385
+ slaId: sla.id,
386
+ period: {
387
+ start: new Date('2024-01-01'),
388
+ end: new Date('2024-01-31'),
389
+ },
390
+ })
391
+
392
+ expect(report.slaId).toBe(sla.id)
393
+ expect(report.period).toBeDefined()
394
+ expect(report.metrics).toBeDefined()
395
+ expect(report.breaches).toBeDefined()
396
+ expect(report.compliancePercentage).toBeDefined()
397
+ })
398
+
399
+ it('should include historical trends', async () => {
400
+ const report = await getSLAReport({
401
+ slaId: 'test-sla',
402
+ period: {
403
+ start: new Date('2024-01-01'),
404
+ end: new Date('2024-03-31'),
405
+ },
406
+ includeHistory: true,
407
+ groupBy: 'month',
408
+ })
409
+
410
+ expect(report.history).toHaveLength(3) // 3 months
411
+ expect(report.history[0].month).toBe('2024-01')
412
+ })
413
+ })
414
+ })