@stacksjs/testing 0.75.361 → 0.75.366
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/dist/index.d.ts +1 -0
- package/dist/index.js +5 -3
- package/dist/schema.d.ts +6 -0
- package/package.json +11 -11
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import D from"process";function me(){D.env.NODE_ENV="test",D.env.APP_ENV="test"}var x=new Set(["headers","query","body","formData","actingAs"]);function
|
|
3
|
-
`).join(""),d},withEnv(i){return r={...r,...i},d},withCwd(i){return s=i,d},withTimeout(i){if(!Number.isFinite(i)||i<=0)throw TypeError(`withTimeout expects a positive number of milliseconds, got ${i}`);return a=i,d},then(i,o){return l().then(i,o)}};return d}import{afterEach as J}from"bun:test";import{events as w}from"@stacksjs/events";var m=null,h=[],
|
|
2
|
+
import D from"process";function me(){D.env.NODE_ENV="test",D.env.APP_ENV="test"}var x=new Set(["headers","query","body","formData","actingAs"]);function N(t){if(!t)return"";let e=new URLSearchParams;for(let[r,s]of Object.entries(t)){if(s===void 0||s===null)continue;if(Array.isArray(s)){for(let a of s)e.append(r,String(a));continue}e.append(r,String(s))}let n=e.toString();return n?`?${n}`:""}function B(t,e,n){let r=Object.keys(t).filter((s)=>!x.has(s));if(r.length===0)return;throw TypeError(`http.${e.toLowerCase()}('${n}', \u2026) got unknown option(s) ${r.map((s)=>`'${s}'`).join(", ")}. Did you mean to send a body? Wrap it: { body: { ${r[0]}: \u2026 } }. `+`Valid options are ${[...x].join(", ")}.`)}async function j(){let t=await import("@stacksjs/router");return t.serverResponse}function U(t){let e={status:t.status,ok:t.ok,headers:t.headers,text:()=>t.text(),json:async()=>await t.clone().json(),assertStatus(n){if(this.status!==n)throw Error(`Expected status ${n}, got ${this.status}`);return e},async assertJson(n){let r=await t.clone().json();for(let[s,a]of Object.entries(n))if(JSON.stringify(r[s])!==JSON.stringify(a))throw Error(`assertJson: expected ${s}=${JSON.stringify(a)}, got ${JSON.stringify(r[s])}`);return e},assertHeader(n,r){let s=t.headers.get(n);if(s===null)throw Error(`assertHeader: missing header '${n}'`);if(r!==void 0){if(!(typeof r==="string"?s===r:r.test(s)))throw Error(`assertHeader: expected ${n}=${String(r)}, got '${s}'`)}return e}};return e}function v(t="http://localhost"){let e=null,n={},r=null;async function s(){if(!e)return null;if(r)return r;let{Auth:o}=await import("@stacksjs/auth"),u=Number(e.id);if(!Number.isFinite(u))throw TypeError(`actingAs() needs a user with a numeric id; got ${JSON.stringify(e.id)}`);let c=await o.loginUsingId(u);if(!c)throw Error(`actingAs() could not authenticate user ${u}: no such user. Create the row first (e.g. via a factory) so a token can be issued against it.`);return r=String(c.token),r}async function a(o){let u={Accept:"application/json",...n,...o.headers};if(o.body!==void 0&&o.formData===void 0)u["Content-Type"]="application/json";let c=await s();if(c&&!u.Authorization&&!u.authorization)u.Authorization=`Bearer ${c}`;return u}function l(o){if(o.formData!==void 0){if(o.body!==void 0)throw TypeError("A request carries either `body` or `formData`, not both.");let u=new FormData;for(let[c,p]of Object.entries(o.formData))u.append(c,p);return u}if(o.body===void 0)return;return typeof o.body==="string"?o.body:JSON.stringify(o.body)}async function d(o,u,c={}){let p=await j(),f=u.startsWith("http")?u:`${t}${u}`,b=N(c.query),I=b&&f.includes("?")?`${f}&${b.slice(1)}`:`${f}${b}`,_={method:o,headers:await a(c),body:l(c)},L=await p(new Request(I,_));return U(L)}let i={actingAs(o){return e=o,r=null,i},withHeaders(o){return n={...n,...o},i},request:(o,u,c)=>d(o,u,c),get:(o)=>d("GET",o),post:(o,u)=>d("POST",o,{body:u}),put:(o,u)=>d("PUT",o,{body:u}),patch:(o,u)=>d("PATCH",o,{body:u}),delete:(o,u)=>d("DELETE",o,{body:u})};return i}function ge(t,e){return v(e).actingAs(t)}var we=Object.freeze(["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"].reduce((t,e)=>{let n=async(r,s={})=>{B(s,e,r);let{actingAs:a,...l}=s,d=v();if(a)d.actingAs(a);return d.request(e,r,l)};return{...t,[e.toLowerCase()]:n}},{}));import k from"process";var H=60000;function M(t){let e=[],n="",r=null,s=!1,a=!1;for(let l of t){if(s){n+=l,s=!1;continue}if(l==="\\"){s=!0,a=!0;continue}if(r){if(l===r)r=null;else n+=l;continue}if(l==='"'||l==="'"){r=l,a=!0;continue}if(/\s/.test(l)){if(a)e.push(n),n="",a=!1;continue}n+=l,a=!0}if(r)throw Error(`Unterminated ${r} in command: ${t}`);if(a)e.push(n);return e}function ye(t){let e=typeof t==="string"?M(t):[...t],n,r={},s=k.cwd(),a=H;async function l(){if(e.length===0)throw Error("command() needs something to run");let i=Bun.spawn(["./buddy",...e],{cwd:s,env:{...k.env,...r},stdin:n===void 0?"ignore":new TextEncoder().encode(n),stdout:"pipe",stderr:"pipe"}),o=!1,u=setTimeout(()=>{o=!0,i.kill()},a);try{let[c,p,f]=await Promise.all([new Response(i.stdout).text(),new Response(i.stderr).text(),i.exited]);return{exitCode:f,stdout:c,stderr:p,output:c+p,timedOut:o}}finally{clearTimeout(u)}}let d={withInput(i){return n=i.map((o)=>`${o}
|
|
3
|
+
`).join(""),d},withEnv(i){return r={...r,...i},d},withCwd(i){return s=i,d},withTimeout(i){if(!Number.isFinite(i)||i<=0)throw TypeError(`withTimeout expects a positive number of milliseconds, got ${i}`);return a=i,d},then(i,o){return l().then(i,o)}};return d}import{afterEach as J}from"bun:test";import{events as w}from"@stacksjs/events";var m=null,h=[],S=(t,e)=>{h.push({type:String(t),payload:e})};function De(){if(m)return;m=[...w.all.entries()],w.all.clear(),h=[],w.on("*",S)}function xe(){return m!==null}function G(t){if(!m)throw Error('getDispatchedEvents() was called without eventFake(). Nothing is recording, so an empty result would look like "the event did not fire".');return t===void 0?[...h]:h.filter((e)=>e.type===t)}function ve(t){return G(t).length>0}function Q(){if(!m)return;w.off("*",S),w.all.clear();for(let[t,e]of m)w.all.set(t,e);m=null,h=[]}J(()=>{Q()});import{afterEach as z}from"bun:test";import{CaptureEmailDriver as y,mail as O}from"@stacksjs/email";var g=null;function Pe(){if(g!==null)return;g=O.fake(),y.clear()}function Ae(){return g!==null}function P(){if(g===null)throw Error('sentEmails() was called without mailFake(). Nothing is capturing, so an empty result would look like "no email was sent".');return[...y.all()]}function $e(){return P().at(-1)}function Ce(t){let e=(n)=>{if(n===void 0||n===null)return!1;return(Array.isArray(n)?n:[n]).some((s)=>(typeof s==="string"?s:s?.address)===t)};return P().filter((n)=>e(n.to)||e(n.cc)||e(n.bcc))}function V(){if(g===null)return;O.restoreDriver(g),g=null,y.clear()}z(()=>{V()});import{setSystemTime as A}from"bun:test";function K(t){let e=t instanceof Date?t:new Date(t);if(Number.isNaN(e.getTime()))throw TypeError(`Not a valid time: ${JSON.stringify(t)}`);return e}function W(t=new Date){let e=K(t);return A(e),e}function Ie(t){return W(t)}function _e(){A()}import{existsSync as X,mkdtempSync as Y,rmSync as Z,writeFileSync as C}from"fs";import{tmpdir as ee}from"os";import{join as T,resolve as te}from"path";import R from"process";var ne=2000;function re(t,e={}){let n=e.mode&&e.mode!=="auto"?e.mode:/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(t)?"test":"run";return[R.execPath,...e.config?[`--config=${e.config}`]:[],...e.envFile===!1?["--no-env-file"]:[],...n==="test"?["test"]:[],t,...e.args??[]]}function se(t){let e=t.inheritEnv===!1?{}:{...R.env};for(let[n,r]of Object.entries(t.env??{}))if(r===void 0)delete e[n];else e[n]=r;return Object.fromEntries(Object.entries(e).filter((n)=>n[1]!==void 0))}async function He(t,e={}){if(!t||typeof t!=="string")throw TypeError("runIsolated needs a file to run");let n=e.timeoutMs??60000;if(!Number.isFinite(n)||n<=0)throw RangeError("timeoutMs must be a positive number of milliseconds");if(e.bunfig!=null&&e.preload)throw TypeError("runIsolated takes bunfig or preload, not both: list the preloads in that bunfig");let r=null,s;if(e.bunfig!=null){if(!e.bunfig)throw TypeError("bunfig must be a path to a bunfig.toml");if(s=te(e.cwd??R.cwd(),e.bunfig),!X(s))throw Error(`runIsolated: no bunfig at ${s}`)}if(e.preload){r=Y(T(ee(),"stacks-isolated-"));let i=T(r,"noop-preload.ts");C(i,`export {}
|
|
4
4
|
`);let o=JSON.stringify(e.preload.length?e.preload:[i]);s=T(r,"bunfig.toml"),C(s,`preload = ${o}
|
|
5
5
|
|
|
6
6
|
[test]
|
|
@@ -9,4 +9,6 @@ preload = ${o}
|
|
|
9
9
|
--- stdout ---
|
|
10
10
|
${t.stdout}
|
|
11
11
|
--- stderr ---
|
|
12
|
-
${t.stderr}`;if(t.timedOut)throw Error(`${t.command.slice(1).join(" ")} timed out after ${t.durationMs}ms${n}`);if(t.code!==0)throw Error(`${t.command.slice(1).join(" ")} exited ${t.code??t.signal}${n}`);if(e!=null&&!(typeof e==="string"?t.stdout.includes(e):e.test(t.stdout)))throw Error(`${t.command.slice(1).join(" ")} did not print ${String(e)}${n}`);return t}import{mkdtempSync as oe,rmSync as E}from"fs";import{tmpdir as ie}from"os";import{join as F}from"path";import ae from"process";var{SQL:q}=globalThis.Bun;var ue=new Set(["localhost","127.0.0.1","[::1]","::1"]);function le(t={}){return t.url||ae.env.STACKS_TEST_POSTGRES_URL||void 0}function de(t){let e;try{e=new URL(t)}catch{throw Error("A disposable Postgres database needs a postgres:// URL")}if(!["postgres:","postgresql:"].includes(e.protocol))throw Error(`A disposable Postgres database needs a postgres:// URL, got ${e.protocol}`);if(!ue.has(e.hostname))throw Error(`Disposable databases are only created on a local PostgreSQL server (localhost, 127.0.0.1, ::1), not "${e.hostname}"`);return e}function ce(t){return`${(t.toLowerCase().replace(/[^a-z0-9_]/g,"_").replace(/^[^a-z_]+/,"")||"stacks_test").slice(0,38)}_${crypto.randomUUID().replaceAll("-","").slice(0,24)}`}async function pe(t,e={}){if(t!=="sqlite"&&t!=="postgres")throw Error(`Disposable databases are sqlite or postgres, not "${String(t)}"`);let n=ce(e.prefix??"stacks_test"),r=oe(F(ie(),`${n.slice(0,20)}-`)),s=!1;if(t==="sqlite"){let a=F(r,`${n}.sqlite`),l=async()=>{if(s)return;s=!0,E(r,{recursive:!0,force:!0})};return{dialect:t,name:n,directory:r,path:a,env:{DB_CONNECTION:"sqlite",DB_DATABASE_PATH:a},drop:l,[Symbol.asyncDispose]:l}}try{let a=le(e);if(!a)throw Error("A disposable Postgres database needs a server: pass options.url or set STACKS_TEST_POSTGRES_URL");let l=de(a),d=new q(l.href);try{await d.unsafe(`CREATE DATABASE "${n}"`)}finally{await d.close()}let i=new URL(l.href);i.pathname=`/${n}`;let o=async()=>{if(s)return;s=!0;let u=new q(l.href);try{await u.unsafe(`DROP DATABASE IF EXISTS "${n}" WITH (FORCE)`)}finally{await u.close(),E(r,{recursive:!0,force:!0})}};return{dialect:t,name:n,directory:r,url:i.href,env:{DB_CONNECTION:"postgres",DB_HOST:l.hostname.replace(/^\[|\]$/g,""),DB_PORT:l.port||"5432",DB_DATABASE:n,DB_USERNAME:decodeURIComponent(l.username),DB_PASSWORD:decodeURIComponent(l.password)},drop:o,[Symbol.asyncDispose]:o}}catch(a){throw E(r,{recursive:!0,force:!0}),a}}async function We(t,e,n={}){let r=await pe(t,n),s;try{s=await e(r)}catch(a){throw await r.drop().catch(()=>{}),a}return await r.drop(),s}
|
|
12
|
+
${t.stderr}`;if(t.timedOut)throw Error(`${t.command.slice(1).join(" ")} timed out after ${t.durationMs}ms${n}`);if(t.code!==0)throw Error(`${t.command.slice(1).join(" ")} exited ${t.code??t.signal}${n}`);if(e!=null&&!(typeof e==="string"?t.stdout.includes(e):e.test(t.stdout)))throw Error(`${t.command.slice(1).join(" ")} did not print ${String(e)}${n}`);return t}import{mkdtempSync as oe,rmSync as E}from"fs";import{tmpdir as ie}from"os";import{join as F}from"path";import ae from"process";var{SQL:q}=globalThis.Bun;var ue=new Set(["localhost","127.0.0.1","[::1]","::1"]);function le(t={}){return t.url||ae.env.STACKS_TEST_POSTGRES_URL||void 0}function de(t){let e;try{e=new URL(t)}catch{throw Error("A disposable Postgres database needs a postgres:// URL")}if(!["postgres:","postgresql:"].includes(e.protocol))throw Error(`A disposable Postgres database needs a postgres:// URL, got ${e.protocol}`);if(!ue.has(e.hostname))throw Error(`Disposable databases are only created on a local PostgreSQL server (localhost, 127.0.0.1, ::1), not "${e.hostname}"`);return e}function ce(t){return`${(t.toLowerCase().replace(/[^a-z0-9_]/g,"_").replace(/^[^a-z_]+/,"")||"stacks_test").slice(0,38)}_${crypto.randomUUID().replaceAll("-","").slice(0,24)}`}async function pe(t,e={}){if(t!=="sqlite"&&t!=="postgres")throw Error(`Disposable databases are sqlite or postgres, not "${String(t)}"`);let n=ce(e.prefix??"stacks_test"),r=oe(F(ie(),`${n.slice(0,20)}-`)),s=!1;if(t==="sqlite"){let a=F(r,`${n}.sqlite`),l=async()=>{if(s)return;s=!0,E(r,{recursive:!0,force:!0})};return{dialect:t,name:n,directory:r,path:a,env:{DB_CONNECTION:"sqlite",DB_DATABASE_PATH:a},drop:l,[Symbol.asyncDispose]:l}}try{let a=le(e);if(!a)throw Error("A disposable Postgres database needs a server: pass options.url or set STACKS_TEST_POSTGRES_URL");let l=de(a),d=new q(l.href);try{await d.unsafe(`CREATE DATABASE "${n}"`)}finally{await d.close()}let i=new URL(l.href);i.pathname=`/${n}`;let o=async()=>{if(s)return;s=!0;let u=new q(l.href);try{await u.unsafe(`DROP DATABASE IF EXISTS "${n}" WITH (FORCE)`)}finally{await u.close(),E(r,{recursive:!0,force:!0})}};return{dialect:t,name:n,directory:r,url:i.href,env:{DB_CONNECTION:"postgres",DB_HOST:l.hostname.replace(/^\[|\]$/g,""),DB_PORT:l.port||"5432",DB_DATABASE:n,DB_USERNAME:decodeURIComponent(l.username),DB_PASSWORD:decodeURIComponent(l.password)},drop:o,[Symbol.asyncDispose]:o}}catch(a){throw E(r,{recursive:!0,force:!0}),a}}async function We(t,e,n={}){let r=await pe(t,n),s;try{s=await e(r)}catch(a){throw await r.drop().catch(()=>{}),a}return await r.drop(),s}async function Ye(t){if(!t.length)return;let{db:e,getDatabaseDialect:n}=await import("@stacksjs/database/runtime"),r=n();if(r!=="sqlite"&&r!=="postgres")throw Error("Atomic test schema setup requires SQLite or PostgreSQL");await e.transaction(async(s)=>{if(r==="postgres")await s.unsafe(t.map((a)=>`${a}
|
|
13
|
+
;`).join(`
|
|
14
|
+
`));else for(let a of t)await s.unsafe(a)})}export*from"bun:test";export{ge as actingAs,Ye as applyTestSchema,Me as assertIsolatedOk,de as assertLocalPostgres,B as assertRequestOptions,ye as command,pe as createDisposableDatabase,le as disposablePostgresUrl,Ce as emailsTo,De as eventFake,xe as eventsAreFaked,v as featureTest,W as freezeTime,G as getDispatchedEvents,ve as hasDispatchedEvent,we as http,re as isolatedCommand,$e as lastEmail,Pe as mailFake,Ae as mailIsFaked,N as queryString,Q as restoreEvents,V as restoreMail,He as runIsolated,P as sentEmails,me as setupTestEnvironment,M as splitCommand,Ie as travelTo,_e as useRealTime,We as withDisposableDatabase};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Apply transaction-compatible schema statements to a configured test database.
|
|
2
|
+
* Use with a disposable database in an isolated process, after initializing its
|
|
3
|
+
* database config. This does not discover models, disable constraints, migrate
|
|
4
|
+
* production data or change durability settings.
|
|
5
|
+
*/
|
|
6
|
+
export declare function applyTestSchema(statements: readonly string[]): Promise<void>;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/testing",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.366",
|
|
6
6
|
"description": "The Stacks way of testing.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -79,16 +79,16 @@
|
|
|
79
79
|
"prepublishOnly": "bun run build"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@stacksjs/auth": "0.75.
|
|
83
|
-
"@stacksjs/cache": "0.75.
|
|
84
|
-
"@stacksjs/config": "0.75.
|
|
85
|
-
"@stacksjs/database": "0.75.
|
|
86
|
-
"@stacksjs/email": "0.75.
|
|
87
|
-
"@stacksjs/events": "0.75.
|
|
88
|
-
"@stacksjs/path": "0.75.
|
|
89
|
-
"@stacksjs/router": "0.75.
|
|
90
|
-
"@stacksjs/storage": "0.75.
|
|
91
|
-
"@stacksjs/strings": "0.75.
|
|
82
|
+
"@stacksjs/auth": "0.75.366",
|
|
83
|
+
"@stacksjs/cache": "0.75.366",
|
|
84
|
+
"@stacksjs/config": "0.75.366",
|
|
85
|
+
"@stacksjs/database": "0.75.366",
|
|
86
|
+
"@stacksjs/email": "0.75.366",
|
|
87
|
+
"@stacksjs/events": "0.75.366",
|
|
88
|
+
"@stacksjs/path": "0.75.366",
|
|
89
|
+
"@stacksjs/router": "0.75.366",
|
|
90
|
+
"@stacksjs/storage": "0.75.366",
|
|
91
|
+
"@stacksjs/strings": "0.75.366",
|
|
92
92
|
"@stacksjs/ts-cloud": "^0.16.69",
|
|
93
93
|
"very-happy-dom": "^0.4.1"
|
|
94
94
|
},
|