|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// issue #225 PR 1 — wasm e2e for the typed-wasm Http skeleton. |
| 3 | +// |
| 4 | +// Inlines the generic Http host adapter (the reusable package is |
| 5 | +// deferred until the Ephapax convergence-ABI review, typed-wasm#31). |
| 6 | +// Implements the #205 protocol: http_request_thenable registers a |
| 7 | +// Promise keyed by an i32 handle; the harness (the host) resolves it |
| 8 | +// and asserts the round-trip. `globalThis.fetch` is stubbed — no |
| 9 | +// network. Mirrors the tests/codegen/*.mjs inline-imports style. |
| 10 | +import assert from 'node:assert/strict'; |
| 11 | +import { readFile } from 'node:fs/promises'; |
| 12 | + |
| 13 | +// ── stubbed host fetch (no network) ────────────────────────────────── |
| 14 | +globalThis.fetch = async (url, init) => ({ |
| 15 | + status: url.includes('/missing') ? 404 : 200, |
| 16 | + headers: { forEach: (cb) => cb('text/plain', 'content-type') }, |
| 17 | + text: async () => `ok:${init && init.method}`, |
| 18 | +}); |
| 19 | + |
| 20 | +let inst = null; |
| 21 | +const _handles = new Map(); |
| 22 | +let _next = 1; |
| 23 | +const _results = new Map(); |
| 24 | + |
| 25 | +function readString(ptr) { |
| 26 | + const dv = new DataView(inst.exports.memory.buffer); |
| 27 | + const len = dv.getUint32(ptr, true); |
| 28 | + const bytes = new Uint8Array(inst.exports.memory.buffer, ptr + 4, len); |
| 29 | + return new TextDecoder('utf-8').decode(bytes); |
| 30 | +} |
| 31 | + |
| 32 | +// The generic Http convergence adapter (host import surface). |
| 33 | +const imports = { |
| 34 | + wasi_snapshot_preview1: { fd_write: () => 0 }, |
| 35 | + Http: { |
| 36 | + http_request_thenable: (urlPtr, methodPtr, bodyPtr) => { |
| 37 | + const url = readString(urlPtr); |
| 38 | + const method = readString(methodPtr); |
| 39 | + const body = readString(bodyPtr); |
| 40 | + const h = _next++; |
| 41 | + const p = globalThis |
| 42 | + .fetch(url, { method, body: body || undefined }) |
| 43 | + .then(async (r) => { |
| 44 | + const headers = []; |
| 45 | + r.headers.forEach((v, k) => headers.push([k, v])); |
| 46 | + return { status: r.status, headers, body: await r.text() }; |
| 47 | + }) |
| 48 | + .catch((e) => ({ __error: String(e) })); |
| 49 | + _handles.set(h, p); |
| 50 | + p.then((v) => _results.set(h, v)); |
| 51 | + return h; |
| 52 | + }, |
| 53 | + }, |
| 54 | +}; |
| 55 | + |
| 56 | +const buf = await readFile('./tests/codegen/http_thenable_skeleton.wasm'); |
| 57 | +const m = await WebAssembly.instantiate(buf, imports); |
| 58 | +inst = m.instance; |
| 59 | + |
| 60 | +// Pure pass-through: launch() returns the Thenable handle. |
| 61 | +const handle = inst.exports.launch(); |
| 62 | +assert.ok(Number.isInteger(handle) && handle > 0, 'launch returns an i32 Thenable handle'); |
| 63 | + |
| 64 | +// Host resolves it via the protocol (guest-side resolution is PR 2/3). |
| 65 | +const settled = await _handles.get(handle); |
| 66 | +assert.equal(settled.status, 200, 'response status round-trips'); |
| 67 | +assert.equal(settled.body, 'ok:GET', 'method + body round-trip via readString'); |
| 68 | +assert.deepEqual(settled.headers, [['content-type', 'text/plain']], 'headers assoc list'); |
| 69 | +assert.equal(_results.get(handle).status, 200, 'thenableResultJson-equivalent payload stored'); |
| 70 | + |
| 71 | +console.log('test_http_thenable_skeleton.mjs OK'); |
0 commit comments