socketduct 0.1.24 → 0.1.26
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/AGENTS.md +2 -2
- package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
- package/package.json +2 -2
- package/scripts/test.js +9 -14
- package/src/client/multiplexed-transport-set.js +20 -2
- package/src/logging/compressed-log-writer.js +1 -1
- package/src/logging/zstd-frame-codec.js +15 -1
- package/src/reverse/client.js +44 -1
- package/src/reverse/hub.js +10 -1
- package/src/reverse/runtime.js +11 -2
- package/test/e2e/gateway-process.test.js +93 -92
- package/test/e2e/logger-shutdown-process.test.js +40 -37
- package/test/e2e/reverse-client-recovery-process.test.js +107 -103
- package/test/e2e/reverse-gateway-process.test.js +328 -318
- package/test/integration/initial-target-recovery.test.js +333 -334
- package/test/integration/logging-cli.test.js +121 -119
- package/test/integration/logging-compressed-recovery.test.js +96 -95
- package/test/integration/multiplexed-compatibility.test.js +178 -177
- package/test/integration/multiplexed-corrections.test.js +360 -359
- package/test/integration/multiplexed-failures.test.js +253 -251
- package/test/integration/multiplexed-transport-set.test.js +334 -322
- package/test/integration/multiplexed-transport.test.js +135 -134
- package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
- package/test/integration/reverse-gateway.test.js +2309 -2218
- package/test/integration/reverse-resume-continuity.test.js +219 -218
- package/test/integration/reverse-route-class.test.js +363 -278
- package/test/integration/socketduct-http-agent.test.js +314 -312
- package/test/integration/socketduct-https-agent.test.js +516 -485
- package/test/integration/stream-compression.test.js +862 -824
- package/test/integration/tcp-forwarding.test.js +2550 -2447
- package/test/integration/tls-forwarding.test.js +593 -592
- package/test/unit/auth/auth.test.js +43 -44
- package/test/unit/auth/static-token-store.test.js +9 -8
- package/test/unit/client/connect.test.js +41 -40
- package/test/unit/client/deferred-virtual-socket.test.js +95 -94
- package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
- package/test/unit/client/multiplexed-transport-set.test.js +845 -732
- package/test/unit/client/socketduct-socket.test.js +768 -771
- package/test/unit/client/virtual-socket.test.js +198 -168
- package/test/unit/config/docker-gateway-files.test.js +224 -224
- package/test/unit/config/environment.test.js +472 -492
- package/test/unit/index.test.js +53 -52
- package/test/unit/logging/compressed-log-worker.test.js +23 -22
- package/test/unit/logging/compressed-log-writer.test.js +155 -154
- package/test/unit/logging/log-retention.test.js +52 -47
- package/test/unit/logging/log-segment-index.test.js +84 -81
- package/test/unit/logging/zstd-frame-codec.test.js +28 -19
- package/test/unit/protocol/compression.test.js +254 -298
- package/test/unit/protocol/frame.test.js +280 -276
- package/test/unit/protocol/v3-compression.test.js +21 -23
- package/test/unit/protocol/v3-frame.test.js +42 -41
- package/test/unit/release-configuration.test.js +15 -12
- package/test/unit/reverse/disconnect-attribution.test.js +65 -64
- package/test/unit/reverse-terminal-outcomes.test.js +53 -50
- package/test/unit/server/control-handler.test.js +232 -231
- package/test/unit/server/primary.test.js +51 -49
- package/test/unit/server/relay-runtime.test.js +280 -294
- package/test/unit/sessions/session.test.js +48 -62
- package/test/unit/spool/spool-file.test.js +401 -385
|
@@ -1,63 +1,68 @@
|
|
|
1
1
|
/* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
|
|
2
|
-
import
|
|
2
|
+
import {describe, expect, it, withTestCleanup} from "@velocious/testing"
|
|
3
3
|
import {readlinkSync} from "node:fs"
|
|
4
4
|
import {mkdtemp, readFile, readdir, rm, utimes, writeFile} from "node:fs/promises"
|
|
5
5
|
import {tmpdir} from "node:os"
|
|
6
6
|
import {join} from "node:path"
|
|
7
|
-
import test from "node:test"
|
|
8
7
|
|
|
9
8
|
import {enforceLogRetention} from "../../../src/logging/log-retention.js"
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
describe("log retention", () => {
|
|
11
|
+
it("retention removes only oldest sealed segments and preserves active evidence", async () => withTestCleanup(async (after) => {
|
|
12
|
+
const directory = await mkdtemp(join(tmpdir(), "socketduct-retention-"))
|
|
13
|
+
after(() => rm(directory, {recursive: true, force: true}))
|
|
14
|
+
const old = await createSegment(directory, "reverse-hub-AAAAAAAAAAAAAAAAAAAAAA-123e4567-e89b-42d3-a456-426614174000", 700, true)
|
|
15
|
+
const newer = await createSegment(directory, "reverse-hub-BBBBBBBBBBBBBBBBBBBBBB-123e4567-e89b-42d3-a456-426614174001", 700, true)
|
|
16
|
+
const active = await createSegment(directory, "reverse-hub-CCCCCCCCCCCCCCCCCCCCCC-123e4567-e89b-42d3-a456-426614174002", 700, false)
|
|
17
|
+
await utimes(old.data, new Date(1_000), new Date(1_000))
|
|
18
|
+
await utimes(newer.data, new Date(2_000), new Date(2_000))
|
|
19
|
+
await utimes(active.data, new Date(3_000), new Date(3_000))
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
21
|
+
const result = await enforceLogRetention({directory, maxAgeMs: 60_000, maxBytes: 1800, now: () => new Date(3_000)})
|
|
22
|
+
const names = await readdir(directory)
|
|
23
|
+
expect(names.some((name) => name.startsWith(old.name))).toBe(false)
|
|
24
|
+
expect(names.some((name) => name.startsWith(newer.name))).toBeTruthy()
|
|
25
|
+
expect(names.some((name) => name.startsWith(active.name))).toBeTruthy()
|
|
26
|
+
expect(result.deletedSegments).toBe(1)
|
|
27
|
+
}))
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
it("retention fails closed when active or quarantined bytes prevent quota compliance", async () => withTestCleanup(async (after) => {
|
|
30
|
+
const directory = await mkdtemp(join(tmpdir(), "socketduct-retention-blocked-"))
|
|
31
|
+
after(() => rm(directory, {recursive: true, force: true}))
|
|
32
|
+
const active = await createSegment(directory, "reverse-client-DDDDDDDDDDDDDDDDDDDDDD-123e4567-e89b-42d3-a456-426614174003", 1800, false)
|
|
33
|
+
await writeFile(`${active.data}.quarantine`, "LOG_EVIDENCE_INVALID", {mode: 0o600})
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
35
|
+
let error
|
|
36
|
+
try {
|
|
37
|
+
await enforceLogRetention({directory, maxAgeMs: 60_000, maxBytes: 1024, now: () => new Date(3_000)})
|
|
38
|
+
} catch (caught) {
|
|
39
|
+
error = caught
|
|
40
|
+
}
|
|
41
|
+
expect(error).toBeDefined()
|
|
42
|
+
expect(error instanceof Error && /** @type {{code?: unknown}} */ (error).code === "LOG_RETENTION_QUOTA_BLOCKED").toBeTruthy()
|
|
43
|
+
expect((await readdir(directory)).some((name) => name.startsWith(active.name))).toBeTruthy()
|
|
44
|
+
}))
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
it("retention reclaims stale ownership without displacing a live process instance", async () => withTestCleanup(async (after) => {
|
|
47
|
+
const directory = await mkdtemp(join(tmpdir(), "socketduct-retention-owner-"))
|
|
48
|
+
after(() => rm(directory, {recursive: true, force: true}))
|
|
49
|
+
const lockPath = join(directory, ".socketduct-retention.lock")
|
|
50
|
+
const processStart = processStartIdentity(await readFile(`/proc/${process.pid}/stat`, "utf8"))
|
|
51
|
+
const tid = Number(readlinkSync("/proc/thread-self").split("/").at(-1))
|
|
52
|
+
const threadStart = processStartIdentity(await readFile(`/proc/${process.pid}/task/${tid}/stat`, "utf8"))
|
|
53
|
+
const staleTemporary = `.socketduct-retention-owner-${process.pid}-${BigInt(processStart) + 1n}-${tid}-${threadStart}-123e4567-e89b-42d3-a456-426614174099.tmp`
|
|
54
|
+
await writeFile(join(directory, staleTemporary), "incomplete", {mode: 0o600})
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
await writeFile(lockPath, `${JSON.stringify({version: 1, pid: process.pid, processStart: String(BigInt(processStart) + 1n), tid, threadStart})}\n`, {mode: 0o600})
|
|
57
|
+
expect((await enforceLogRetention({directory, maxAgeMs: 60_000, maxBytes: 1024})).skipped).toBe(false)
|
|
58
|
+
expect((await readdir(directory)).includes(staleTemporary)).toBe(false)
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
await writeFile(lockPath, `${JSON.stringify({version: 1, pid: process.pid, processStart, tid: 2_147_483_647, threadStart: "1"})}\n`, {mode: 0o600})
|
|
61
|
+
expect((await enforceLogRetention({directory, maxAgeMs: 60_000, maxBytes: 1024})).skipped).toBe(false)
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
await writeFile(lockPath, `${JSON.stringify({version: 1, pid: process.pid, processStart, tid, threadStart})}\n`, {mode: 0o600})
|
|
64
|
+
expect((await enforceLogRetention({directory, maxAgeMs: 60_000, maxBytes: 1024})).skipped).toBe(true)
|
|
65
|
+
}))
|
|
61
66
|
})
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -80,7 +85,7 @@ function processStartIdentity(value) {
|
|
|
80
85
|
const close = value.lastIndexOf(")")
|
|
81
86
|
const fields = value.slice(close + 2).trim().split(/\s+/)
|
|
82
87
|
const start = fields[19]
|
|
83
|
-
if (start === undefined)
|
|
84
|
-
|
|
88
|
+
if (start === undefined) throw new Error("process stat omits start time")
|
|
89
|
+
expect(start).toMatch(/^[0-9]+$/)
|
|
85
90
|
return start
|
|
86
91
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {describe, expect, it} from "@velocious/testing"
|
|
2
2
|
import {createHash, randomBytes} from "node:crypto"
|
|
3
|
-
import test from "node:test"
|
|
4
3
|
|
|
5
4
|
import {
|
|
6
5
|
INDEX_ENTRY_BYTES,
|
|
@@ -12,93 +11,97 @@ import {
|
|
|
12
11
|
uuidBytes
|
|
13
12
|
} from "../../../src/logging/log-segment-index.js"
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
14
|
+
describe("log segment index", () => {
|
|
15
|
+
it("index v1 header and entry round-trip in the exact fixed-width format", () => {
|
|
16
|
+
const segmentUuid = uuidBytes("123e4567-e89b-42d3-a456-426614174000")
|
|
17
|
+
const writerEpoch = randomBytes(16)
|
|
18
|
+
const batchId = randomBytes(16)
|
|
19
|
+
const frameHash = createHash("sha256").update("frame").digest()
|
|
20
|
+
const header = createIndexHeader({segmentUuid, writerEpoch})
|
|
21
|
+
const entry = createIndexEntry({
|
|
22
|
+
segmentUuid,
|
|
23
|
+
writerEpoch,
|
|
24
|
+
batchId,
|
|
25
|
+
firstSequence: 1n,
|
|
26
|
+
lastSequence: 3n,
|
|
27
|
+
dataOffset: 0n,
|
|
28
|
+
compressedLength: 91n,
|
|
29
|
+
uncompressedLength: 240n,
|
|
30
|
+
recordCount: 3,
|
|
31
|
+
minTimestamp: 1_795_000_000_000n,
|
|
32
|
+
maxTimestamp: 1_795_000_000_010n,
|
|
33
|
+
frameSha256: frameHash
|
|
34
|
+
})
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
segmentUuid,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
expect(header.length).toBe(INDEX_HEADER_BYTES)
|
|
37
|
+
expect(entry.length).toBe(INDEX_ENTRY_BYTES)
|
|
38
|
+
const parsedHeader = parseIndexHeader(header)
|
|
39
|
+
expect(parsedHeader.version).toBe(1)
|
|
40
|
+
expect(Buffer.from(parsedHeader.segmentUuid).equals(Buffer.from(segmentUuid))).toBe(true)
|
|
41
|
+
expect(Buffer.from(parsedHeader.writerEpoch).equals(Buffer.from(writerEpoch))).toBe(true)
|
|
42
|
+
const parsedEntry = parseIndexEntry(entry, {segmentUuid, writerEpoch})
|
|
43
|
+
expect(parsedEntry.version).toBe(1)
|
|
44
|
+
expect(parsedEntry.flags).toBe(0)
|
|
45
|
+
expect(Buffer.from(parsedEntry.segmentUuid).equals(Buffer.from(segmentUuid))).toBe(true)
|
|
46
|
+
expect(Buffer.from(parsedEntry.writerEpoch).equals(Buffer.from(writerEpoch))).toBe(true)
|
|
47
|
+
expect(Buffer.from(parsedEntry.batchId).equals(Buffer.from(batchId))).toBe(true)
|
|
48
|
+
expect(parsedEntry.firstSequence).toBe(1n)
|
|
49
|
+
expect(parsedEntry.lastSequence).toBe(3n)
|
|
50
|
+
expect(parsedEntry.dataOffset).toBe(0n)
|
|
51
|
+
expect(parsedEntry.compressedLength).toBe(91n)
|
|
52
|
+
expect(parsedEntry.uncompressedLength).toBe(240n)
|
|
53
|
+
expect(parsedEntry.recordCount).toBe(3)
|
|
54
|
+
expect(parsedEntry.minTimestamp).toBe(1_795_000_000_000n)
|
|
55
|
+
expect(parsedEntry.maxTimestamp).toBe(1_795_000_000_010n)
|
|
56
|
+
expect(Buffer.from(parsedEntry.frameSha256).equals(Buffer.from(frameHash))).toBe(true)
|
|
54
57
|
})
|
|
55
|
-
})
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
it("index CRC and identity corruption fail closed", () => {
|
|
60
|
+
const segmentUuid = randomBytes(16)
|
|
61
|
+
const writerEpoch = randomBytes(16)
|
|
62
|
+
const header = createIndexHeader({segmentUuid, writerEpoch})
|
|
63
|
+
header[20] = /** @type {number} */ (header[20]) ^ 0xff
|
|
64
|
+
expect(() => parseIndexHeader(header)).toThrow(/CRC32C/)
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
segmentUuid,
|
|
66
|
-
writerEpoch,
|
|
67
|
-
batchId: randomBytes(16),
|
|
68
|
-
firstSequence: 8n,
|
|
69
|
-
lastSequence: 8n,
|
|
70
|
-
dataOffset: 10n,
|
|
71
|
-
compressedLength: 20n,
|
|
72
|
-
uncompressedLength: 30n,
|
|
73
|
-
recordCount: 1,
|
|
74
|
-
minTimestamp: 4n,
|
|
75
|
-
maxTimestamp: 4n,
|
|
76
|
-
frameSha256: randomBytes(32)
|
|
77
|
-
})
|
|
78
|
-
entry[151] = /** @type {number} */ (entry[151]) ^ 0xff
|
|
79
|
-
assert.throws(() => parseIndexEntry(entry, {segmentUuid, writerEpoch}), /CRC32C/)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test("index bytes contain no searchable diagnostic dimensions", () => {
|
|
83
|
-
const forbidden = ["minisforum-zyxel", "disconnect-incident", "GATEWAY_DISCONNECTED", "docker.internal"]
|
|
84
|
-
const segmentUuid = randomBytes(16)
|
|
85
|
-
const writerEpoch = randomBytes(16)
|
|
86
|
-
const encoded = Buffer.concat([
|
|
87
|
-
createIndexHeader({segmentUuid, writerEpoch}),
|
|
88
|
-
createIndexEntry({
|
|
66
|
+
const entry = createIndexEntry({
|
|
89
67
|
segmentUuid,
|
|
90
68
|
writerEpoch,
|
|
91
69
|
batchId: randomBytes(16),
|
|
92
|
-
firstSequence:
|
|
93
|
-
lastSequence:
|
|
94
|
-
dataOffset:
|
|
95
|
-
compressedLength:
|
|
96
|
-
uncompressedLength:
|
|
70
|
+
firstSequence: 8n,
|
|
71
|
+
lastSequence: 8n,
|
|
72
|
+
dataOffset: 10n,
|
|
73
|
+
compressedLength: 20n,
|
|
74
|
+
uncompressedLength: 30n,
|
|
97
75
|
recordCount: 1,
|
|
98
|
-
minTimestamp:
|
|
99
|
-
maxTimestamp:
|
|
76
|
+
minTimestamp: 4n,
|
|
77
|
+
maxTimestamp: 4n,
|
|
100
78
|
frameSha256: randomBytes(32)
|
|
101
79
|
})
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
entry[151] = /** @type {number} */ (entry[151]) ^ 0xff
|
|
81
|
+
expect(() => parseIndexEntry(entry, {segmentUuid, writerEpoch})).toThrow(/CRC32C/)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("index bytes contain no searchable diagnostic dimensions", () => {
|
|
85
|
+
const forbidden = ["minisforum-zyxel", "disconnect-incident", "GATEWAY_DISCONNECTED", "docker.internal"]
|
|
86
|
+
const segmentUuid = randomBytes(16)
|
|
87
|
+
const writerEpoch = randomBytes(16)
|
|
88
|
+
const encoded = Buffer.concat([
|
|
89
|
+
createIndexHeader({segmentUuid, writerEpoch}),
|
|
90
|
+
createIndexEntry({
|
|
91
|
+
segmentUuid,
|
|
92
|
+
writerEpoch,
|
|
93
|
+
batchId: randomBytes(16),
|
|
94
|
+
firstSequence: 1n,
|
|
95
|
+
lastSequence: 1n,
|
|
96
|
+
dataOffset: 0n,
|
|
97
|
+
compressedLength: 1n,
|
|
98
|
+
uncompressedLength: 1n,
|
|
99
|
+
recordCount: 1,
|
|
100
|
+
minTimestamp: 1n,
|
|
101
|
+
maxTimestamp: 1n,
|
|
102
|
+
frameSha256: randomBytes(32)
|
|
103
|
+
})
|
|
104
|
+
])
|
|
105
|
+
for (const value of forbidden) expect(encoded.includes(Buffer.from(value))).toBe(false)
|
|
106
|
+
})
|
|
104
107
|
})
|
|
@@ -1,24 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import test from "node:test"
|
|
1
|
+
import {describe, expect, it} from "@velocious/testing"
|
|
3
2
|
|
|
4
|
-
import {compressLogFrame, decompressLogFrame} from "../../../src/logging/zstd-frame-codec.js"
|
|
3
|
+
import {DEFAULT_LOG_COMPRESSION_LEVEL, compressLogFrame, decompressLogFrame} from "../../../src/logging/zstd-frame-codec.js"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
describe("zstd log frame codec", () => {
|
|
6
|
+
it("emits independently decodable standard zstd frames with checksums at the default level", async () => {
|
|
7
|
+
const firstRaw = Buffer.from('{"event":"first"}\n')
|
|
8
|
+
const secondRaw = Buffer.from('{"event":"second"}\n')
|
|
9
|
+
const first = await compressLogFrame(firstRaw)
|
|
10
|
+
const second = await compressLogFrame(secondRaw)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
12
|
+
expect(first.subarray(0, 4).toString("hex")).toBe("28b52ffd")
|
|
13
|
+
expect(Number(first[4]) & 0x04).toBeGreaterThan(0)
|
|
14
|
+
expect((await decompressLogFrame(first)).toString("hex")).toBe(firstRaw.toString("hex"))
|
|
15
|
+
expect((await decompressLogFrame(second)).toString("hex")).toBe(secondRaw.toString("hex"))
|
|
16
|
+
expect(first.equals(second)).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("never falls back to plaintext", async () => {
|
|
20
|
+
const raw = Buffer.from("small\n")
|
|
21
|
+
const frame = await compressLogFrame(raw)
|
|
22
|
+
|
|
23
|
+
expect(frame.equals(raw)).toBe(false)
|
|
24
|
+
await expect(decompressLogFrame(Buffer.from("small\n"))).rejects.toThrow()
|
|
25
|
+
})
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
it("default compression level stays memory-bounded", () => {
|
|
28
|
+
// A fresh zstd context per batch costs ~1-2 MB at level 9, tens of MB at 12
|
|
29
|
+
// and ~90 MB at 19. Keep the default in the bounded range unless a
|
|
30
|
+
// deliberate review accepts the resident-memory consequences.
|
|
31
|
+
expect(DEFAULT_LOG_COMPRESSION_LEVEL).toBeLessThanOrEqual(9)
|
|
32
|
+
})
|
|
24
33
|
})
|