@strav/testing 1.0.0-alpha.24 → 1.0.0-alpha.25
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/testing",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.25",
|
|
4
4
|
"description": "Strav testing utilities — in-memory stream, typed fetch stub, Postgres availability probe + schema reset, bootTestApp orchestrator, stub providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.ts",
|
|
10
10
|
"./brain": "./src/brain/index.ts",
|
|
11
|
+
"./cache": "./src/cache/index.ts",
|
|
11
12
|
"./postgres": "./src/postgres/index.ts"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
@@ -21,11 +22,11 @@
|
|
|
21
22
|
"access": "public"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@strav/database": "1.0.0-alpha.
|
|
25
|
-
"@strav/kernel": "1.0.0-alpha.
|
|
25
|
+
"@strav/database": "1.0.0-alpha.25",
|
|
26
|
+
"@strav/kernel": "1.0.0-alpha.25"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
|
-
"@strav/brain": "1.0.0-alpha.
|
|
29
|
+
"@strav/brain": "1.0.0-alpha.25",
|
|
29
30
|
"@types/bun": ">=1.3.14"
|
|
30
31
|
},
|
|
31
32
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cheap connection probe over Bun's TCP socket — opens a connection,
|
|
3
|
+
* sends `version\r\n`, expects a `VERSION ...\r\n` reply. Cached for
|
|
4
|
+
* the lifetime of the test process.
|
|
5
|
+
*
|
|
6
|
+
* Returns `false` if `MEMCACHED_HOST` / `MEMCACHED_PORT` are missing
|
|
7
|
+
* OR the probe fails. Pair with
|
|
8
|
+
* `describe.skipIf(!await isMemcachedAvailable())`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let cachedAvailability: boolean | null = null
|
|
12
|
+
|
|
13
|
+
export async function isMemcachedAvailable(): Promise<boolean> {
|
|
14
|
+
if (cachedAvailability !== null) return cachedAvailability
|
|
15
|
+
const host = process.env['MEMCACHED_HOST']
|
|
16
|
+
const portStr = process.env['MEMCACHED_PORT']
|
|
17
|
+
if (host === undefined || host === '' || portStr === undefined || portStr === '') {
|
|
18
|
+
cachedAvailability = false
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
const port = Number(portStr)
|
|
22
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
23
|
+
cachedAvailability = false
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
cachedAvailability = await probe(host, port)
|
|
28
|
+
} catch {
|
|
29
|
+
cachedAvailability = false
|
|
30
|
+
}
|
|
31
|
+
return cachedAvailability
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function probe(host: string, port: number): Promise<boolean> {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
let settled = false
|
|
37
|
+
const finish = (ok: boolean): void => {
|
|
38
|
+
if (settled) return
|
|
39
|
+
settled = true
|
|
40
|
+
resolve(ok)
|
|
41
|
+
}
|
|
42
|
+
const timeout = setTimeout(() => finish(false), 2_000)
|
|
43
|
+
void Bun.connect({
|
|
44
|
+
hostname: host,
|
|
45
|
+
port,
|
|
46
|
+
socket: {
|
|
47
|
+
open(socket) {
|
|
48
|
+
socket.write('version\r\n')
|
|
49
|
+
},
|
|
50
|
+
data(socket, chunk) {
|
|
51
|
+
const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)
|
|
52
|
+
const text = new TextDecoder().decode(bytes)
|
|
53
|
+
const ok = text.startsWith('VERSION')
|
|
54
|
+
// Settle before closing — `socket.end()` synchronously fires
|
|
55
|
+
// the `close` callback, which would beat `finish(ok)` to the
|
|
56
|
+
// settle line and report a false negative.
|
|
57
|
+
clearTimeout(timeout)
|
|
58
|
+
finish(ok)
|
|
59
|
+
socket.end()
|
|
60
|
+
},
|
|
61
|
+
error(_socket, _error) {
|
|
62
|
+
clearTimeout(timeout)
|
|
63
|
+
finish(false)
|
|
64
|
+
},
|
|
65
|
+
close() {
|
|
66
|
+
clearTimeout(timeout)
|
|
67
|
+
if (!settled) finish(false)
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}).catch(() => {
|
|
71
|
+
clearTimeout(timeout)
|
|
72
|
+
finish(false)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cheap connection probe — opens `Bun.RedisClient` against
|
|
3
|
+
* `REDIS_URL`, sends `PING`, reports. Cached for the lifetime of the
|
|
4
|
+
* test process.
|
|
5
|
+
*
|
|
6
|
+
* Returns `false` if `REDIS_URL` is missing OR the connection / PING
|
|
7
|
+
* fails. Pair with `describe.skipIf(!await isRedisAvailable())`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { RedisClient } from 'bun'
|
|
11
|
+
|
|
12
|
+
let cachedAvailability: boolean | null = null
|
|
13
|
+
|
|
14
|
+
export async function isRedisAvailable(): Promise<boolean> {
|
|
15
|
+
if (cachedAvailability !== null) return cachedAvailability
|
|
16
|
+
const url = process.env['REDIS_URL']
|
|
17
|
+
if (url === undefined || url === '') {
|
|
18
|
+
cachedAvailability = false
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
let client: RedisClient | undefined
|
|
22
|
+
try {
|
|
23
|
+
client = new RedisClient(url)
|
|
24
|
+
// `send('PING', [])` is supported on every Bun.RedisClient build —
|
|
25
|
+
// safer than `ping()` which isn't on the typed surface.
|
|
26
|
+
const reply = await client.send('PING', [])
|
|
27
|
+
cachedAvailability = reply === 'PONG' || reply === 'OK' || typeof reply === 'string'
|
|
28
|
+
return cachedAvailability
|
|
29
|
+
} catch {
|
|
30
|
+
cachedAvailability = false
|
|
31
|
+
return false
|
|
32
|
+
} finally {
|
|
33
|
+
try {
|
|
34
|
+
client?.close()
|
|
35
|
+
} catch {
|
|
36
|
+
// Already closed / never connected — nothing to clean.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
CHANGED