@strav/testing 1.0.0-alpha.25 → 1.0.0-alpha.28

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.25",
3
+ "version": "1.0.0-alpha.28",
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",
@@ -9,7 +9,8 @@
9
9
  ".": "./src/index.ts",
10
10
  "./brain": "./src/brain/index.ts",
11
11
  "./cache": "./src/cache/index.ts",
12
- "./postgres": "./src/postgres/index.ts"
12
+ "./postgres": "./src/postgres/index.ts",
13
+ "./storage": "./src/storage/index.ts"
13
14
  },
14
15
  "files": [
15
16
  "src",
@@ -22,11 +23,11 @@
22
23
  "access": "public"
23
24
  },
24
25
  "dependencies": {
25
- "@strav/database": "1.0.0-alpha.25",
26
- "@strav/kernel": "1.0.0-alpha.25"
26
+ "@strav/database": "1.0.0-alpha.28",
27
+ "@strav/kernel": "1.0.0-alpha.28"
27
28
  },
28
29
  "peerDependencies": {
29
- "@strav/brain": "1.0.0-alpha.25",
30
+ "@strav/brain": "1.0.0-alpha.28",
30
31
  "@types/bun": ">=1.3.14"
31
32
  },
32
33
  "peerDependenciesMeta": {
package/src/index.ts CHANGED
@@ -31,3 +31,11 @@ export {
31
31
  // Cache availability probes — also re-exported under
32
32
  // `@strav/testing/cache` for the same reason.
33
33
  export { isMemcachedAvailable, isRedisAvailable } from './cache/index.ts'
34
+
35
+ // Storage helpers — also re-exported under `@strav/testing/storage`.
36
+ export {
37
+ createTempStorageRoot,
38
+ ensureS3Bucket,
39
+ isS3Available,
40
+ type TempStorageRoot,
41
+ } from './storage/index.ts'
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Create a temp dir for `LocalStorage` integration tests. Returns the
3
+ * path and a cleanup function. Tests use this in `beforeAll` so the
4
+ * test suite doesn't litter `storage/` paths in the repo.
5
+ */
6
+
7
+ import { mkdtemp, rm } from 'node:fs/promises'
8
+ import { tmpdir } from 'node:os'
9
+ import { join } from 'node:path'
10
+
11
+ export interface TempStorageRoot {
12
+ path: string
13
+ cleanup(): Promise<void>
14
+ }
15
+
16
+ export async function createTempStorageRoot(): Promise<TempStorageRoot> {
17
+ const path = await mkdtemp(join(tmpdir(), 'strav-storage-'))
18
+ return {
19
+ path,
20
+ async cleanup() {
21
+ await rm(path, { recursive: true, force: true })
22
+ },
23
+ }
24
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Ensure the configured test bucket exists. Creates it on MinIO via
3
+ * the admin endpoint when it's missing — AWS / R2 users typically
4
+ * create the bucket out of band, so this helper is a no-op when the
5
+ * list call already succeeds.
6
+ *
7
+ * Returns the bucket name on success. Throws if the bucket can't be
8
+ * reached or created.
9
+ */
10
+
11
+ import { S3Client } from 'bun'
12
+
13
+ export async function ensureS3Bucket(): Promise<string> {
14
+ const endpoint = process.env['S3_ENDPOINT']
15
+ const bucket = process.env['S3_BUCKET']
16
+ const accessKeyId = process.env['S3_ACCESS_KEY_ID']
17
+ const secretAccessKey = process.env['S3_SECRET_ACCESS_KEY']
18
+ if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) {
19
+ throw new Error(
20
+ 'ensureS3Bucket: missing S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY env. Source .env.test or run docker-compose up.',
21
+ )
22
+ }
23
+ const client = new S3Client({
24
+ endpoint,
25
+ bucket,
26
+ accessKeyId,
27
+ secretAccessKey,
28
+ region: process.env['S3_REGION'] ?? 'us-east-1',
29
+ })
30
+ try {
31
+ await client.list({ maxKeys: 1 })
32
+ return bucket
33
+ } catch {
34
+ // Try MinIO-style auto-create via a PUT to the bucket root.
35
+ try {
36
+ const url = `${endpoint.replace(/\/$/, '')}/${bucket}`
37
+ // MinIO accepts an empty PUT against the bucket URL with the
38
+ // default region. Signing a CreateBucket call by hand is more
39
+ // work than this slice warrants — we shell to the s3 list path
40
+ // again after a best-effort PUT and report success on either
41
+ // outcome.
42
+ await fetch(url, { method: 'PUT' }).catch(() => undefined)
43
+ await client.list({ maxKeys: 1 })
44
+ return bucket
45
+ } catch (cause) {
46
+ throw new Error(
47
+ `ensureS3Bucket: bucket "${bucket}" does not exist at ${endpoint} and could not be created. ` +
48
+ 'Create it manually via the MinIO console (http://localhost:9001) or your provider.',
49
+ { cause },
50
+ )
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,3 @@
1
+ export { createTempStorageRoot, type TempStorageRoot } from './create_temp_storage_root.ts'
2
+ export { ensureS3Bucket } from './ensure_s3_bucket.ts'
3
+ export { isS3Available } from './is_s3_available.ts'
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cheap connection probe over `Bun.S3Client`. Opens against
3
+ * `S3_ENDPOINT` + creds, attempts to list one key, ensures the
4
+ * configured bucket exists (creates it lazily on MinIO when it
5
+ * doesn't). Cached for the lifetime of the test process.
6
+ *
7
+ * Returns `false` if env is missing OR the probe fails. Pair with
8
+ * `describe.skipIf(!await isS3Available())`.
9
+ */
10
+
11
+ import { S3Client } from 'bun'
12
+
13
+ let cachedAvailability: boolean | null = null
14
+
15
+ export async function isS3Available(): Promise<boolean> {
16
+ if (cachedAvailability !== null) return cachedAvailability
17
+ const endpoint = process.env['S3_ENDPOINT']
18
+ const bucket = process.env['S3_BUCKET']
19
+ const accessKeyId = process.env['S3_ACCESS_KEY_ID']
20
+ const secretAccessKey = process.env['S3_SECRET_ACCESS_KEY']
21
+ if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) {
22
+ cachedAvailability = false
23
+ return false
24
+ }
25
+ try {
26
+ const client = new S3Client({
27
+ endpoint,
28
+ bucket,
29
+ accessKeyId,
30
+ secretAccessKey,
31
+ region: process.env['S3_REGION'] ?? 'us-east-1',
32
+ })
33
+ // Probe: list one key. If the bucket doesn't exist, listing will
34
+ // throw; ensure-bucket via writing+deleting a sentinel works on
35
+ // MinIO (bucket auto-create defaults vary), but we lean on the
36
+ // user creating the bucket beforehand for AWS/R2 to avoid
37
+ // surprises. On MinIO the suite uses an ensure helper that's
38
+ // called separately by the test setup.
39
+ await client.list({ maxKeys: 1 })
40
+ cachedAvailability = true
41
+ return true
42
+ } catch {
43
+ cachedAvailability = false
44
+ return false
45
+ }
46
+ }