From 3a136d4277eceee2d2532b8367fef60cb71da0c2 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Fri, 13 Oct 2023 16:40:47 -0400 Subject: [PATCH] feat(sdk)!: expose simulator via HTTP server (#4105) This PR evolves the Wing simulator so that every simulation spins up an HTTP server. This HTTP server accepts requests to call inflight methods on resources, making it possible for simulated resources to interact across multiple processes on the same machine (such as from containers running on your system). Since it's not possible to serialize all forms of data as HTTP, there's a small regression where the `Redis` class's `rawClient` API no longer worked. For the time being, I've removed the method to work around the issue. We might be able to recover the functionality by writing a dedicated implementation of `toInflight` for the `sim` target's implementation of `Redis`, but it's a bit beyond the scope of this PR. BREAKING CHANGE: The `rawClient()` method on `ex.Redis` has been removed. This API was infrequently used and was provided without any type information. If you have a use case for this API, let us know! ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [ ] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- Cargo.lock | 22 +-- docs/docs/02-concepts/05-simulator.md | 28 ++++ docs/docs/04-standard-library/02-ex/redis.md | 18 --- examples/tests/sdk_tests/bucket/delete.test.w | 4 +- .../tests/sdk_tests/bucket/metadata.test.w | 9 +- .../tests/sdk_tests/bucket/public_url.test.w | 4 +- examples/tests/sdk_tests/queue/push.test.w | 6 +- .../tests/sdk_tests/schedule/on_tick.test.w | 26 ++-- examples/tests/sdk_tests/table/get.test.w | 2 +- examples/tests/valid/redis.test.w | 6 - libs/wingsdk/.projen/deps.json | 4 + libs/wingsdk/.projen/tasks.json | 2 +- libs/wingsdk/.projenrc.ts | 1 + libs/wingsdk/package.json | 2 + libs/wingsdk/src/ex/redis.ts | 14 +- libs/wingsdk/src/shared/bundling.ts | 5 + libs/wingsdk/src/simulator/client.ts | 92 +++++++++++++ libs/wingsdk/src/simulator/index.ts | 1 + libs/wingsdk/src/simulator/simulator.ts | 127 +++++++++++++++++- .../src/target-sim/function.inflight.ts | 5 +- libs/wingsdk/src/target-sim/redis.inflight.ts | 2 +- .../src/target-sim/service.inflight.ts | 5 +- libs/wingsdk/src/target-sim/util.ts | 17 ++- .../src/target-tf-aws/redis.inflight.ts | 2 +- .../__snapshots__/simulator.test.ts.snap | 4 +- libs/wingsdk/test/simulator/simulator.test.ts | 4 +- .../__snapshots__/bucket.test.ts.snap | 3 +- .../__snapshots__/file-counter.test.ts.snap | 30 +++-- .../__snapshots__/function.test.ts.snap | 4 +- .../immutable-capture.test.ts.snap | 123 +++++++++++------ .../__snapshots__/queue.test.ts.snap | 24 ++-- libs/wingsdk/test/target-sim/bucket.test.ts | 73 ++-------- .../test/target-sim/file-counter.test.ts | 98 +++++++------- libs/wingsdk/test/target-sim/function.test.ts | 8 +- .../test/target-sim/immutable-capture.test.ts | 2 +- libs/wingsdk/test/target-sim/queue.test.ts | 9 +- libs/wingsdk/test/target-sim/redis.test.ts | 17 +-- pnpm-lock.yaml | 21 ++- .../bucket/delete.test.w_compile_tf-aws.md | 2 +- .../bucket/metadata.test.w_compile_tf-aws.md | 9 +- .../public_url.test.w_compile_tf-aws.md | 2 +- .../queue/push.test.w_compile_tf-aws.md | 4 +- .../schedule/on_tick.test.w_compile_tf-aws.md | 10 +- .../table/get.test.w_compile_tf-aws.md | 2 +- .../valid/redis.test.w_compile_tf-aws.md | 6 +- 45 files changed, 543 insertions(+), 316 deletions(-) create mode 100644 libs/wingsdk/src/simulator/client.ts diff --git a/Cargo.lock b/Cargo.lock index 89a16ce8e05..e750e4a9f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -131,9 +131,9 @@ checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" dependencies = [ "libc", ] @@ -378,13 +378,13 @@ checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.2.16", "windows-sys 0.48.0", ] @@ -396,9 +396,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", @@ -554,9 +554,9 @@ checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" [[package]] name = "insta" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686" +checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" dependencies = [ "console", "lazy_static", diff --git a/docs/docs/02-concepts/05-simulator.md b/docs/docs/02-concepts/05-simulator.md index f03312360de..1c028dca775 100644 --- a/docs/docs/02-concepts/05-simulator.md +++ b/docs/docs/02-concepts/05-simulator.md @@ -94,6 +94,34 @@ const response = await fn.invoke("hello!"); console.log(response); ``` +### Interacting with the simulation across processes + +The simulator also exposes an HTTP server that can be used to interact with the +simulated application from other processes. To get the URL of the simulator's +HTTP server, access `sim.url` after the simulator has started. + +```typescript +declare const sim: testing.Simulator; + +// ...after the simulator has started + +const url = sim.url; +console.log(url); + +const handleId = sim.getResourceConfig("root/Default/cloud.Bucket").attrs.handle; +console.log(handleId); +``` + +In another process, a client can be generated to interact with the simulator. Internally it will behave the same as the resource client, but it will use HTTP requests to communicate with the simulator. + +```typescript +import { cloud, testing } from '@winglang/sdk'; + +const bucket = testing.makeSimulatorClient("WING_SIMULATOR_URL", "HANDLE_ID") as cloud.IBucketClient; +const response = await bucket.get("my-key"); +console.log(response); +``` + ### Debugging Finally, when you want to understand how Wing resources are working, you may diff --git a/docs/docs/04-standard-library/02-ex/redis.md b/docs/docs/04-standard-library/02-ex/redis.md index 3e2931a868a..ed06856a79e 100644 --- a/docs/docs/04-standard-library/02-ex/redis.md +++ b/docs/docs/04-standard-library/02-ex/redis.md @@ -36,7 +36,6 @@ new ex.Redis(); | get | Get value at given key. | | hget | Returns the value associated with field in the hash stored at key. | | hset | Sets the specified field to respective value in the hash stored at key. | -| rawClient | Get raw redis client (currently IoRedis). | | sadd | Add the specified members to the set stored at key. | | set | Set key value pair. | | smembers | Returns all the members of the set value stored at key. | @@ -132,14 +131,6 @@ value to set at field in key. --- -##### `rawClient` - -```wing -inflight rawClient(): any -``` - -Get raw redis client (currently IoRedis). - ##### `sadd` ```wing @@ -263,7 +254,6 @@ new ex.RedisClientBase(); | get | Get value at given key. | | hget | Returns the value associated with field in the hash stored at key. | | hset | Sets the specified field to respective value in the hash stored at key. | -| rawClient | Get raw redis client (currently IoRedis). | | sadd | Add the specified members to the set stored at key. | | set | Set key value pair. | | smembers | Returns all the members of the set value stored at key. | @@ -345,14 +335,6 @@ Sets the specified field to respective value in the hash stored at key. --- -##### `rawClient` - -```wing -rawClient(): any -``` - -Get raw redis client (currently IoRedis). - ##### `sadd` ```wing diff --git a/examples/tests/sdk_tests/bucket/delete.test.w b/examples/tests/sdk_tests/bucket/delete.test.w index 9572072f123..e8324b13f16 100644 --- a/examples/tests/sdk_tests/bucket/delete.test.w +++ b/examples/tests/sdk_tests/bucket/delete.test.w @@ -9,7 +9,7 @@ test "delete" { try { block(); } catch actual { - assert(actual == expected); + assert(actual.contains(expected)); error = true; } assert(error); @@ -34,4 +34,4 @@ test "delete" { b.delete("file2.txt"); assert(!b.exists("file2.txt")); -} \ No newline at end of file +} diff --git a/examples/tests/sdk_tests/bucket/metadata.test.w b/examples/tests/sdk_tests/bucket/metadata.test.w index 3584614b0ec..6bb88233bfd 100644 --- a/examples/tests/sdk_tests/bucket/metadata.test.w +++ b/examples/tests/sdk_tests/bucket/metadata.test.w @@ -5,13 +5,14 @@ let b = new cloud.Bucket(); test "metadata" { b.put("test1.txt", "Foo"); - assert(b.metadata("test1.txt").size == 3); - assert(b.metadata("test1.txt").contentType == "application/octet-stream"); - assert(b.metadata("test1.txt").lastModified.year >= 2023); + let metadata = b.metadata("test1.txt"); + assert(metadata.size == 3); + assert(metadata.contentType == "application/octet-stream"); + assert(metadata.lastModified.year >= 2023); try { b.metadata("no-such-file.txt").lastModified; } catch e { - assert(e == "Object does not exist (key=no-such-file.txt)."); + assert(e.contains("Object does not exist (key=no-such-file.txt).")); } } diff --git a/examples/tests/sdk_tests/bucket/public_url.test.w b/examples/tests/sdk_tests/bucket/public_url.test.w index 94d8d8d240d..3d151dbe528 100644 --- a/examples/tests/sdk_tests/bucket/public_url.test.w +++ b/examples/tests/sdk_tests/bucket/public_url.test.w @@ -11,7 +11,7 @@ test "publicUrl" { try { block(); } catch actual { - assert(actual == expected); + assert(actual.contains(expected)); error = true; } assert(error); @@ -33,4 +33,4 @@ test "publicUrl" { assertThrows(BUCKET_NOT_PUBLIC_ERROR, () => { privateBucket.publicUrl("file2.txt"); }); -} \ No newline at end of file +} diff --git a/examples/tests/sdk_tests/queue/push.test.w b/examples/tests/sdk_tests/queue/push.test.w index e6837594ea7..ed80133ed0a 100644 --- a/examples/tests/sdk_tests/queue/push.test.w +++ b/examples/tests/sdk_tests/queue/push.test.w @@ -17,14 +17,14 @@ new std.Test(inflight () => { q.push(""); assert(false); } catch e { - assert(e == "Empty messages are not allowed"); + assert(e.contains("Empty messages are not allowed")); } try { q.push("Foo", ""); assert(false); } catch e { - assert(e == "Empty messages are not allowed"); + assert(e.contains("Empty messages are not allowed")); } q.push("Foo"); @@ -47,4 +47,4 @@ new std.Test(inflight () => { assert(util.waitUntil((): bool => { return q.approxSize() == 3; })); -}, { timeout: 3m }) as "push"; \ No newline at end of file +}, { timeout: 3m }) as "push"; diff --git a/examples/tests/sdk_tests/schedule/on_tick.test.w b/examples/tests/sdk_tests/schedule/on_tick.test.w index dc682bd5412..00b27d8c13f 100644 --- a/examples/tests/sdk_tests/schedule/on_tick.test.w +++ b/examples/tests/sdk_tests/schedule/on_tick.test.w @@ -7,27 +7,23 @@ let from_rate = new cloud.Schedule( rate: 1m ) as "from_rate"; let c1 = new cloud.Counter() as "c1"; let c2 = new cloud.Counter() as "c2"; - from_cron.onTick(inflight () => { - c1.inc(); + c1.inc(); }); from_rate.onTick(inflight () => { - c2.inc(); + c2.inc(); }); - -// std.Test is used setting the timeout property new std.Test(inflight () => { - // counters start at zero - assert(c1.peek() == 0); - assert(c2.peek() == 0); - - // wait at least one minute - util.sleep(1.1m); + // counters may have been incremented before the test starts + let c1val = c1.peek(); + let c2val = c2.peek(); - // check that both counters have been incremented - assert(c1.peek() >= 1); - assert(c2.peek() >= 1); + // wait at least one minute + util.sleep(1.1m); -}, std.TestProps { timeout: 2m }) as "on tick is called both for rate and cron schedules"; + // check that both counters have been incremented + assert(c1.peek() >= c1val + 1); + assert(c2.peek() >= c2val + 1); +},timeout: 2m) as "on tick is called both for rate and cron schedules"; diff --git a/examples/tests/sdk_tests/table/get.test.w b/examples/tests/sdk_tests/table/get.test.w index fe880c281aa..b2b554479ad 100644 --- a/examples/tests/sdk_tests/table/get.test.w +++ b/examples/tests/sdk_tests/table/get.test.w @@ -20,7 +20,7 @@ test "get" { try { block(); } catch actual { - assert(actual == expected); + assert(actual.contains(expected)); error = true; } assert(error); diff --git a/examples/tests/valid/redis.test.w b/examples/tests/valid/redis.test.w index 93f8378eeff..a8d355a24a2 100644 --- a/examples/tests/valid/redis.test.w +++ b/examples/tests/valid/redis.test.w @@ -18,12 +18,6 @@ queue.setConsumer(inflight (message: str) => { }, timeout: 3s); test "testing Redis" { - // Using raw client - let connection = r.rawClient(); - connection.set("wing", "does redis"); - let value = connection.get("wing"); - assert(value == "does redis"); - // Using API r2.set("wing", "does redis again"); let value2 = r2.get("wing"); diff --git a/libs/wingsdk/.projen/deps.json b/libs/wingsdk/.projen/deps.json index 5c7159ccc78..cd5947ee388 100644 --- a/libs/wingsdk/.projen/deps.json +++ b/libs/wingsdk/.projen/deps.json @@ -295,6 +295,10 @@ "name": "safe-stable-stringify", "type": "bundled" }, + { + "name": "undici", + "type": "bundled" + }, { "name": "uuid", "type": "bundled" diff --git a/libs/wingsdk/.projen/tasks.json b/libs/wingsdk/.projen/tasks.json index 9b02644fc00..acb844e229e 100644 --- a/libs/wingsdk/.projen/tasks.json +++ b/libs/wingsdk/.projen/tasks.json @@ -480,7 +480,7 @@ "exec": "pnpm i --no-frozen-lockfile" }, { - "exec": "pnpm update @cdktf/provider-aws @types/aws-lambda @types/express @types/fs-extra @types/mime-types @types/node @types/uuid @typescript-eslint/eslint-plugin @typescript-eslint/parser @vitest/coverage-v8 @winglang/jsii-docgen aws-sdk-client-mock aws-sdk-client-mock-jest bump-pack cdktf-cli constructs eslint-config-prettier eslint-import-resolver-node eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint-plugin-sort-exports eslint fs-extra jsii-diff jsii-pacmak jsii-rosetta jsii nanoid npm-check-updates prettier projen standard-version ts-node typescript vitest wing-api-checker @aws-sdk/client-cloudwatch-logs @aws-sdk/client-dynamodb @aws-sdk/client-elasticache @aws-sdk/client-lambda @aws-sdk/client-s3 @aws-sdk/client-secrets-manager @aws-sdk/client-sns @aws-sdk/client-sqs @aws-sdk/is-array-buffer @aws-sdk/s3-request-presigner @aws-sdk/types @aws-sdk/util-buffer-from @aws-sdk/util-dynamodb @aws-sdk/util-stream-node @aws-sdk/util-utf8-node @azure/core-paging @azure/identity @azure/storage-blob @types/aws-lambda aws-cdk-lib cdktf cron-parser esbuild-wasm express ioredis jsonschema mime-types nanoid safe-stable-stringify uuid yaml constructs constructs" + "exec": "pnpm update @cdktf/provider-aws @types/aws-lambda @types/express @types/fs-extra @types/mime-types @types/node @types/uuid @typescript-eslint/eslint-plugin @typescript-eslint/parser @vitest/coverage-v8 @winglang/jsii-docgen aws-sdk-client-mock aws-sdk-client-mock-jest bump-pack cdktf-cli constructs eslint-config-prettier eslint-import-resolver-node eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint-plugin-sort-exports eslint fs-extra jsii-diff jsii-pacmak jsii-rosetta jsii nanoid npm-check-updates prettier projen standard-version ts-node typescript vitest wing-api-checker @aws-sdk/client-cloudwatch-logs @aws-sdk/client-dynamodb @aws-sdk/client-elasticache @aws-sdk/client-lambda @aws-sdk/client-s3 @aws-sdk/client-secrets-manager @aws-sdk/client-sns @aws-sdk/client-sqs @aws-sdk/is-array-buffer @aws-sdk/s3-request-presigner @aws-sdk/types @aws-sdk/util-buffer-from @aws-sdk/util-dynamodb @aws-sdk/util-stream-node @aws-sdk/util-utf8-node @azure/core-paging @azure/identity @azure/storage-blob @types/aws-lambda aws-cdk-lib cdktf cron-parser esbuild-wasm express ioredis jsonschema mime-types nanoid safe-stable-stringify undici uuid yaml constructs constructs" }, { "exec": "pnpm exec projen" diff --git a/libs/wingsdk/.projenrc.ts b/libs/wingsdk/.projenrc.ts index ccfe1890f30..eb8758cb08a 100644 --- a/libs/wingsdk/.projenrc.ts +++ b/libs/wingsdk/.projenrc.ts @@ -78,6 +78,7 @@ const project = new cdk.JsiiProject({ // simulator dependencies "express", "uuid", + "undici", // using version 3 because starting from version 4, it no longer works with CommonJS. "nanoid@^3.3.6", "cron-parser", diff --git a/libs/wingsdk/package.json b/libs/wingsdk/package.json index 8af63262534..5d32c9f6de8 100644 --- a/libs/wingsdk/package.json +++ b/libs/wingsdk/package.json @@ -109,6 +109,7 @@ "mime-types": "^2.1.35", "nanoid": "^3.3.6", "safe-stable-stringify": "^2.4.3", + "undici": "^5.26.3", "uuid": "^8.3.2", "yaml": "^2.3.2" }, @@ -142,6 +143,7 @@ "mime-types", "nanoid", "safe-stable-stringify", + "undici", "uuid", "yaml" ], diff --git a/libs/wingsdk/src/ex/redis.ts b/libs/wingsdk/src/ex/redis.ts index d516ea481b0..da39bd001a3 100644 --- a/libs/wingsdk/src/ex/redis.ts +++ b/libs/wingsdk/src/ex/redis.ts @@ -32,7 +32,6 @@ export abstract class Redis extends Resource { /** @internal */ public _getInflightOps(): string[] { return [ - RedisInflightMethods.RAW_CLIENT, RedisInflightMethods.URL, RedisInflightMethods.SET, RedisInflightMethods.GET, @@ -49,12 +48,6 @@ export abstract class Redis extends Resource { * Inflight interface for `Redis`. */ export interface IRedisClient { - /** - * Get raw redis client (currently IoRedis). - * @inflight - */ - rawClient(): Promise; - /** * Get url of redis server. * @inflight @@ -126,8 +119,6 @@ export interface IRedisClient { * @internal */ export enum RedisInflightMethods { - /** `Redis.rawClient` */ - RAW_CLIENT = "rawClient", /** `Redis.url` */ URL = "url", /** `Redis.set` */ @@ -150,7 +141,10 @@ export enum RedisInflightMethods { * Base class for `Redis` Client. */ export abstract class RedisClientBase implements IRedisClient { - public abstract rawClient(): Promise; + /** + * Get raw redis client (currently IoRedis). + */ + protected abstract rawClient(): Promise; public abstract url(): Promise; public async set(key: string, value: string): Promise { diff --git a/libs/wingsdk/src/shared/bundling.ts b/libs/wingsdk/src/shared/bundling.ts index 468a261104b..ecf0ebaf7da 100644 --- a/libs/wingsdk/src/shared/bundling.ts +++ b/libs/wingsdk/src/shared/bundling.ts @@ -10,6 +10,8 @@ export interface Bundle { hash: string; } +const SDK_PATH = normalPath(join(__dirname, "..", "..")); + /** * Bundles a javascript entrypoint into a single file. * @param entrypoint The javascript entrypoint @@ -29,6 +31,9 @@ export function createBundle(entrypoint: string, outputDir?: string): Bundle { nodePaths: process.env.WING_NODE_MODULES ? [normalPath(process.env.WING_NODE_MODULES as string)] : undefined, + alias: { + "@winglang/sdk": SDK_PATH, + }, minify: false, platform: "node", target: "node18", diff --git a/libs/wingsdk/src/simulator/client.ts b/libs/wingsdk/src/simulator/client.ts new file mode 100644 index 00000000000..0f5f93ed7ad --- /dev/null +++ b/libs/wingsdk/src/simulator/client.ts @@ -0,0 +1,92 @@ +import { fetch, Agent } from "undici"; +import type { + SimulatorServerRequest, + SimulatorServerResponse, +} from "./simulator"; +import { Datetime } from "../std"; + +// TODO: more robust serialization scheme + +export function serializeValue(input: any): string { + return JSON.stringify(input, (_key, value) => { + if (value instanceof Datetime) { + return { + $kind: "datetime", + day: value.dayOfMonth, + hour: value.hours, + min: value.min, + month: value.month, + sec: value.sec, + year: value.year, + ms: value.ms, + tz: value.timezone, + }; + } + return value; + }); +} + +export function deserializeValue(input: string): any { + return JSON.parse(input, (_key, value) => { + // assumption: Wing APIs don't distinguish between null and undefined, so we can swap them + if (value === null) { + return undefined; + } + if (value.$kind === "datetime") { + return Datetime.fromComponents({ + day: value.day, + hour: value.hour, + min: value.min, + month: value.month, + sec: value.sec, + year: value.year, + ms: value.ms, + tz: value.tz, + }); + } + return value; + }); +} + +export function makeSimulatorClient(url: string, handle: string) { + return new Proxy( + {}, + { + get: function (_target, method, _receiver) { + return async function (...args: any[]) { + const body: SimulatorServerRequest = { + handle, + method: method as string, + args, + }; + let resp; + try { + resp = await fetch(url + "/v1/call", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + dispatcher: new Agent({ + keepAliveTimeout: 15 * 60 * 1000, + keepAliveMaxTimeout: 15 * 60 * 1000, + headersTimeout: 15 * 60 * 1000, + bodyTimeout: 15 * 60 * 1000, + }), + }); + } catch (e) { + console.error(e); + throw e; + } + + let parsed: SimulatorServerResponse = deserializeValue( + await resp.text() + ); + + if (parsed.error) { + throw new Error(parsed.error); + } + return parsed.result; + }; + }, + } + ); +} diff --git a/libs/wingsdk/src/simulator/index.ts b/libs/wingsdk/src/simulator/index.ts index ec92e67c9b6..b78ccbbdf55 100644 --- a/libs/wingsdk/src/simulator/index.ts +++ b/libs/wingsdk/src/simulator/index.ts @@ -1,3 +1,4 @@ +export * from "./client"; export * from "./simulator"; export * from "./testing"; export * from "./tree"; diff --git a/libs/wingsdk/src/simulator/simulator.ts b/libs/wingsdk/src/simulator/simulator.ts index a214dfb480a..9cdac2be128 100644 --- a/libs/wingsdk/src/simulator/simulator.ts +++ b/libs/wingsdk/src/simulator/simulator.ts @@ -1,5 +1,12 @@ import { existsSync } from "fs"; +import { Server } from "http"; +import { AddressInfo } from "net"; import { join } from "path"; +import { + deserializeValue, + makeSimulatorClient, + serializeValue, +} from "./client"; import { Tree } from "./tree"; import { SDK_VERSION } from "../constants"; import { ConstructTree, TREE_FILE_PATH } from "../core"; @@ -11,6 +18,8 @@ import { isToken } from "../target-sim/tokens"; const START_ATTEMPT_COUNT = 10; +const LOCALHOST_ADDRESS = "127.0.0.1"; + /** * Props for `Simulator`. */ @@ -77,6 +86,11 @@ export interface ISimulatorContext { */ readonly resourcePath: string; + /** + * The url that the simulator server is listening on. + */ + readonly serverUrl: string; + /** * Find a resource simulation by its handle. Throws if the handle isn't valid. */ @@ -127,6 +141,8 @@ export class Simulator { private readonly _traceSubscribers: Array; private _tree: Tree; private _connections: ConnectionData[]; + private _serverUrl: string | undefined; + private _server: Server | undefined; constructor(props: SimulatorProps) { this.simdir = props.simfile; @@ -203,6 +219,8 @@ export class Simulator { ...this._config.resources, ]; + await this.startServer(); + while (true) { const next = initQueue.shift(); if (!next) { @@ -231,6 +249,69 @@ export class Simulator { this._running = true; } + /** + * Start a server that allows any resource to be accessed via HTTP. + */ + public async startServer(): Promise { + // import lazily to reduce SDK import time (20ms or so) + const express = await import("express").then((m) => m.default); + const app = express(); + app.use(express.json()); + + app.post("/v1/call", async (req, res, next) => { + try { + let request: SimulatorServerRequest = deserializeValue( + JSON.stringify(req.body) + ); + + const { handle, method, args } = request; + const resource = this._handles.find(handle); + + try { + const result = await (resource as any)[method](...args); + res + .status(200) + .json( + JSON.parse(serializeValue({ result })) as SimulatorServerResponse + ); + } catch (err) { + if (err instanceof Error) { + res.status(500).json({ + error: err.stack ?? err.message, + } as SimulatorServerResponse); + } else { + res.status(500).json({ error: err } as SimulatorServerResponse); + } + } + } catch (err) { + return next(err); + } + }); + + const addrInfo: AddressInfo = await new Promise((resolve, reject) => { + this._server = app.listen(0, LOCALHOST_ADDRESS, () => { + const addr = this._server?.address(); + if (addr && typeof addr === "object" && (addr as any).port) { + resolve(addr); + } else { + reject(new Error("No address found")); + } + }); + }); + + this._serverUrl = `http://${addrInfo.address}:${addrInfo.port}`; + } + + /** + * The URL that the simulator server is listening on. + */ + public get url(): string { + if (!this._serverUrl) { + throw new Error("Simulator server is not running."); + } + return this._serverUrl; + } + /** * Stop the simulation and clean up all resources. */ @@ -265,6 +346,9 @@ export class Simulator { this._addTrace(event); } + this._server!.close(); + this._server!.closeAllConnections(); + this._handles.reset(); this._running = false; } @@ -299,28 +383,28 @@ export class Simulator { } /** - * Get a simulated resource instance. + * Get a resource client. * @returns the resource */ public getResource(path: string): any { - const handle = this.tryGetResource(path); - if (!handle) { + const client = this.tryGetResource(path); + if (!client) { throw new Error(`Resource "${path}" not found.`); } - return handle; + return client; } /** - * Get a simulated resource instance. + * Get a resource client. * @returns The resource of undefined if not found */ public tryGetResource(path: string): any | undefined { - const handle = this.tryGetResourceConfig(path)?.attrs.handle; + const handle: string = this.tryGetResourceConfig(path)?.attrs.handle; if (!handle) { return undefined; } - return this._handles.find(handle); + return makeSimulatorClient(this.url, handle); } /** @@ -422,9 +506,14 @@ export class Simulator { } private createContext(resourceConfig: BaseResourceSchema): ISimulatorContext { + const serverUrl = this._serverUrl; + if (!serverUrl) { + throw new Error("Simulator server is not running."); + } return { simdir: this.simdir, resourcePath: resourceConfig.path, + serverUrl, findInstance: (handle: string) => { return this._handles.find(handle); }, @@ -653,3 +742,27 @@ export interface ConnectionData { /** A name for the connection. */ readonly name: string; } + +/** + * Internal schema for requests to the simulator server's /call endpoint. + * Subject to breaking changes. + */ +export interface SimulatorServerRequest { + /** The resource handle (an ID unique among resources in the simulation). */ + readonly handle: string; + /** The method to call on the resource. */ + readonly method: string; + /** The arguments to the method. */ + readonly args: any[]; +} + +/** + * Internal schema for responses from the simulator server's /call endpoint. + * Subject to breaking changes. + */ +export interface SimulatorServerResponse { + /** The result of the method call. */ + readonly result?: any; + /** The error that occurred during the method call. */ + readonly error?: any; +} diff --git a/libs/wingsdk/src/target-sim/function.inflight.ts b/libs/wingsdk/src/target-sim/function.inflight.ts index 53b55a152fc..bb93b5cc935 100644 --- a/libs/wingsdk/src/target-sim/function.inflight.ts +++ b/libs/wingsdk/src/target-sim/function.inflight.ts @@ -42,7 +42,10 @@ export class Function implements IFunctionClient, ISimulatorResourceInstance { activity: async () => { const sb = new Sandbox(this.filename, { context: { $simulator: this.context }, - env: this.env, + env: { + ...this.env, + WING_SIMULATOR_URL: this.context.serverUrl, + }, timeout: this.timeout, log: (_level, message) => { this.context.addTrace({ diff --git a/libs/wingsdk/src/target-sim/redis.inflight.ts b/libs/wingsdk/src/target-sim/redis.inflight.ts index 199a8c7dd76..e3d8c755e29 100644 --- a/libs/wingsdk/src/target-sim/redis.inflight.ts +++ b/libs/wingsdk/src/target-sim/redis.inflight.ts @@ -55,7 +55,7 @@ export class Redis await runCommand("docker", ["rm", "-f", `${this.containerName}`]); } - public async rawClient(): Promise { + protected async rawClient(): Promise { if (this.connection) { return this.connection; } diff --git a/libs/wingsdk/src/target-sim/service.inflight.ts b/libs/wingsdk/src/target-sim/service.inflight.ts index bb2b3a15332..e6f02fa1167 100644 --- a/libs/wingsdk/src/target-sim/service.inflight.ts +++ b/libs/wingsdk/src/target-sim/service.inflight.ts @@ -22,7 +22,10 @@ export class Service implements IServiceClient, ISimulatorResourceInstance { this.entrypoint = resolve(context.simdir, props.sourceCodeFile); this.autoStart = props.autoStart; this.sandbox = new Sandbox(this.entrypoint, { - env: props.environmentVariables, + env: { + ...props.environmentVariables, + WING_SIMULATOR_URL: context.serverUrl, + }, context: { $simulator: this.context }, log: (_level, message) => { this.context.addTrace({ diff --git a/libs/wingsdk/src/target-sim/util.ts b/libs/wingsdk/src/target-sim/util.ts index 0965e3dc02c..3398d5dcd9a 100644 --- a/libs/wingsdk/src/target-sim/util.ts +++ b/libs/wingsdk/src/target-sim/util.ts @@ -43,13 +43,20 @@ export function bindSimulatorResource( export function makeSimulatorJsClient(filename: string, resource: Resource) { const type = basename(filename).split(".")[0]; const env = makeEnvVarName(type, resource); - return `(function(env) { - let handle = process.env[env]; + + // return an object where calling any method will make a request to the simulator server + return `(function() { + const handle = process.env.${env}; if (!handle) { - throw new Error("Missing environment variable: " + env); + throw new Error("Missing environment variable: ${env}"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error("Missing environment variable: WING_SIMULATOR_URL"); } - return $simulator.findInstance(handle); -})("${env}")`; + + return require("@winglang/sdk").simulator.makeSimulatorClient(simulatorUrl, handle); +})()`; } // helper function to convert duration to a cron string diff --git a/libs/wingsdk/src/target-tf-aws/redis.inflight.ts b/libs/wingsdk/src/target-tf-aws/redis.inflight.ts index fc1e4061d2f..6e2e9b752aa 100644 --- a/libs/wingsdk/src/target-tf-aws/redis.inflight.ts +++ b/libs/wingsdk/src/target-tf-aws/redis.inflight.ts @@ -48,7 +48,7 @@ export class RedisClient extends RedisClientBase { return cacheNode.Endpoint.Address!; } - public async rawClient(): Promise { + protected async rawClient(): Promise { if (!this.clusterId) { throw new Error("No cluster id provided"); } diff --git a/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap b/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap index c635ef446a0..5efebe83114 100644 --- a/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap +++ b/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap @@ -87,7 +87,6 @@ exports[`run all tests > single test 1`] = ` { "data": { "message": "Invoke (payload=\\"\\").", - "result": undefined, "status": "success", }, "sourcePath": "root/test/Handler", @@ -118,7 +117,6 @@ exports[`run single test > happy path 1`] = ` { "data": { "message": "Invoke (payload=\\"\\").", - "result": undefined, "status": "success", }, "sourcePath": "root/test/Handler", @@ -157,7 +155,7 @@ exports[`run single test > test failure 1`] = ` }, { "data": { - "error": [Error: test failed], + "error": {}, "message": "Invoke (payload=\\"\\").", "status": "failure", }, diff --git a/libs/wingsdk/test/simulator/simulator.test.ts b/libs/wingsdk/test/simulator/simulator.test.ts index 7ae3bd0afcb..6d46903516c 100644 --- a/libs/wingsdk/test/simulator/simulator.test.ts +++ b/libs/wingsdk/test/simulator/simulator.test.ts @@ -1,9 +1,9 @@ import { Construct } from "constructs"; import { test, expect, describe } from "vitest"; -import { Bucket, ITestRunnerClient, TestResult } from "../../src/cloud"; +import { Bucket } from "../../src/cloud"; import { InflightBindings } from "../../src/core"; import { Testing } from "../../src/simulator"; -import { Test } from "../../src/std"; +import { ITestRunnerClient, Test, TestResult } from "../../src/std"; import { SimApp } from "../sim-app"; describe("run single test", () => { diff --git a/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap index 5e0b9ad89cb..8e39678bdfb 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/bucket.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`bucket on event creates 3 topics, and sends the right event and key in the event handlers 1`] = ` +exports[`bucket on event creates 3 topics, and sends the right event and key in the event handlers 1`] = ` [ "wingsdk.cloud.TestRunner created.", "wingsdk.cloud.Topic created.", @@ -575,7 +575,6 @@ exports[`update an object in bucket 1`] = ` "Invoke (payload=\\"greeting.txt\\").", "Subscriber error: TypeError: this.handler.handle is not a function", "Put (key=greeting.txt).", - "Put (key=greeting.txt).", "wingsdk.sim.EventMapping deleted.", "wingsdk.cloud.Function deleted.", "wingsdk.cloud.Bucket deleted.", diff --git a/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap index ff8aee06aee..380304cb7fd 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/file-counter.test.ts.snap @@ -18,20 +18,30 @@ return class Handler { } }; })())({ -counter: (function(env) { - let handle = process.env[env]; +counter: (function() { + const handle = process.env.COUNTER_HANDLE_4ecd8d46; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: COUNTER_HANDLE_4ecd8d46\\"); } - return $simulator.findInstance(handle); -})(\\"COUNTER_HANDLE_4ecd8d46\\"), -bucket: (function(env) { - let handle = process.env[env]; + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})(), +bucket: (function() { + const handle = process.env.BUCKET_HANDLE_5f2a41c8; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_5f2a41c8\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_5f2a41c8\\") + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})() }), args: {} })).handle(event); };", "connections.json": { diff --git a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap index 88895a88d58..dd000b0faab 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap @@ -628,7 +628,7 @@ return class Handler { } async handle() { - return fetch; + return typeof fetch; } }; @@ -648,7 +648,7 @@ return class Handler { async handle() { const c = require(\\"crypto\\"); - return c.createHash; + return typeof c.createHash; } }; diff --git a/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap index f4967452ddd..0338841f29e 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/immutable-capture.test.ts.snap @@ -126,19 +126,29 @@ if (!(await this.my_buckets[1].get(\\"foo\\") === \\"bar\\")) { throw new Error( } }; })())({ -my_buckets: [(function(env) { - let handle = process.env[env]; +my_buckets: [(function() { + const handle = process.env.BUCKET_HANDLE_4fecd6d0; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_4fecd6d0\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_4fecd6d0\\"),(function(env) { - let handle = process.env[env]; + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})(),(function() { + const handle = process.env.BUCKET_HANDLE_300e8055; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_300e8055\\"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_300e8055\\")] + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()] })).handle(event); };", "connections.json": { @@ -1032,19 +1042,29 @@ if (!(await foo.get(\\"hello.txt\\") === \\"world\\")) { throw new Error(\`asser } }; })())({ -my_map: new Map([[\\"foo\\",(function(env) { - let handle = process.env[env]; +my_map: new Map([[\\"foo\\",(function() { + const handle = process.env.BUCKET_HANDLE_4fecd6d0; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_4fecd6d0\\"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_4fecd6d0\\")],[\\"bar\\",(function(env) { - let handle = process.env[env]; + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()],[\\"bar\\",(function() { + const handle = process.env.BUCKET_HANDLE_300e8055; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_300e8055\\"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_300e8055\\")]]) + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()]]) })).handle(event); };", "connections.json": { @@ -1835,37 +1855,62 @@ if (!(await bar.get(\\"foo\\") === \\"bar\\")) { throw new Error(\`assertion fai } }; })())({ -my_struct: {\\"bucky\\": (function(env) { - let handle = process.env[env]; +my_struct: {\\"bucky\\": (function() { + const handle = process.env.BUCKET_HANDLE_4fecd6d0; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_4fecd6d0\\"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_4fecd6d0\\"),\\"mapy\\": new Map([[\\"foo\\",(function(env) { - let handle = process.env[env]; + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})(),\\"mapy\\": new Map([[\\"foo\\",(function() { + const handle = process.env.BUCKET_HANDLE_300e8055; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_300e8055\\"); + } + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_300e8055\\")],[\\"bar\\",(function(env) { - let handle = process.env[env]; + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()],[\\"bar\\",(function() { + const handle = process.env.BUCKET_HANDLE_0120daf4; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_0120daf4\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_0120daf4\\")]]),\\"arry\\": {\\"boom\\": [(function(env) { - let handle = process.env[env]; + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()]]),\\"arry\\": {\\"boom\\": [(function() { + const handle = process.env.BUCKET_HANDLE_ea7944ef; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_ea7944ef\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_ea7944ef\\"),(function(env) { - let handle = process.env[env]; + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})(),(function() { + const handle = process.env.BUCKET_HANDLE_1d7ab674; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: BUCKET_HANDLE_1d7ab674\\"); } - return $simulator.findInstance(handle); -})(\\"BUCKET_HANDLE_1d7ab674\\")],},} + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})()],},} })).handle(event); };", "connections.json": { diff --git a/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap index 2a44d4d5a81..f84af896ca9 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/queue.test.ts.snap @@ -800,7 +800,7 @@ exports[`queue batch size of 2, purge the queue 2`] = ` } `; -exports[`queue with one subscriber, batch size of 5 1`] = ` +exports[`queue with one subscriber, batch size of 5, should get processed in two batches 1`] = ` { ".wing/function_c8ab799f.js": "\\"use strict\\"; exports.handler = async function(event) { @@ -812,22 +812,22 @@ return class Handler { } } async handle() { - await this.queue.push(\\"A\\"); - await this.queue.push(\\"B\\"); - await this.queue.push(\\"C\\"); - await this.queue.push(\\"D\\"); - await this.queue.push(\\"E\\"); - await this.queue.push(\\"F\\"); + await this.queue.push(\\"A\\", \\"B\\", \\"C\\", \\"D\\", \\"E\\", \\"F\\"); } }; })())({ -queue: (function(env) { - let handle = process.env[env]; +queue: (function() { + const handle = process.env.QUEUE_HANDLE_54fcf4cd; if (!handle) { - throw new Error(\\"Missing environment variable: \\" + env); + throw new Error(\\"Missing environment variable: QUEUE_HANDLE_54fcf4cd\\"); } - return $simulator.findInstance(handle); -})(\\"QUEUE_HANDLE_54fcf4cd\\") + const simulatorUrl = process.env.WING_SIMULATOR_URL; + if (!simulatorUrl) { + throw new Error(\\"Missing environment variable: WING_SIMULATOR_URL\\"); + } + + return require(\\"@winglang/sdk\\").simulator.makeSimulatorClient(simulatorUrl, handle); +})() })).handle(event); };", ".wing/my_queue-setconsumer-e645076f_c8ddc1ce.js": "\\"use strict\\"; diff --git a/libs/wingsdk/test/target-sim/bucket.test.ts b/libs/wingsdk/test/target-sim/bucket.test.ts index 29053341e45..f42acb07e83 100644 --- a/libs/wingsdk/test/target-sim/bucket.test.ts +++ b/libs/wingsdk/test/target-sim/bucket.test.ts @@ -47,25 +47,14 @@ test("update an object in bucket", async () => { const VALUE = JSON.stringify({ msg: "Hello world!" }); // WHEN - // @ts-expect-error - private method - const notifyListeners = vi.spyOn(client, "notifyListeners"); - await client.put(KEY, VALUE); - expect(notifyListeners).toBeCalledWith(cloud.BucketEventType.CREATE, KEY); - - await client.put(KEY, JSON.stringify({ msg: "another msg" })); - expect(notifyListeners).toBeCalledWith(cloud.BucketEventType.UPDATE, KEY); - expect(Object.keys((client as any).topicHandlers)).toMatchObject([ - BucketEventType.CREATE, - ]); // THEN await s.stop(); - expect(notifyListeners).toBeCalledTimes(2); expect(listMessages(s)).toMatchSnapshot(); }); -test("bucket on event creates 3 topics, and sends the right event and key in the event handlers ", async () => { +test("bucket on event creates 3 topics, and sends the right event and key in the event handlers", async () => { // GIVEN const app = new SimApp(); const bucket = cloud.Bucket._newBucket(app, "my_bucket"); @@ -73,9 +62,9 @@ test("bucket on event creates 3 topics, and sends the right event and key in the const testInflight = Testing.makeHandler( app, "inflight_test", - `async handle(key, event) { await this.bucket.put(key, event); }`, + `async handle(key, event) { await this.logBucket.put(key, event); }`, { - bucket: { + logBucket: { obj: logBucket, ops: [cloud.BucketInflightMethods.PUT], }, @@ -88,12 +77,6 @@ test("bucket on event creates 3 topics, and sends the right event and key in the const client = s.getResource("/my_bucket") as cloud.IBucketClient; const logClient = s.getResource("/log_bucket") as cloud.IBucketClient; - expect(Object.keys((client as any).topicHandlers)).toMatchObject([ - BucketEventType.CREATE, - BucketEventType.UPDATE, - BucketEventType.DELETE, - ]); - // THEN await client.put("a", "1"); expect(await logClient.get("a")).toBe(BucketEventType.CREATE); @@ -167,8 +150,6 @@ test("put and get Json object from bucket", async () => { const VALUE = { msg: "Hello world!" }; // WHEN - - const notifyListeners = vi.spyOn(client as any, "notifyListeners"); await client.putJson(KEY, VALUE as any); const response = await client.getJson("greeting.json"); @@ -178,7 +159,6 @@ test("put and get Json object from bucket", async () => { expect(response).toEqual(VALUE); expect(listMessages(s)).toMatchSnapshot(); expect(app.snapshot()).toMatchSnapshot(); - expect(notifyListeners).toBeCalledWith(cloud.BucketEventType.CREATE, KEY); }); test("put multiple objects and list all from bucket", async () => { @@ -351,20 +331,9 @@ test("removing a key will call onDelete method", async () => { await client.put(fileName, JSON.stringify({ msg: "Hello world!" })); // delete file - //@ts-expect-error - const notifyListeners = vi.spyOn(client, "notifyListeners"); const response = await client.delete(fileName); - - expect(Object.keys((client as any).topicHandlers)).toMatchObject([ - BucketEventType.DELETE, - ]); - await s.stop(); - expect(notifyListeners).toBeCalledWith( - cloud.BucketEventType.DELETE, - fileName - ); expect(response).toEqual(undefined); expect(listMessages(s)).toMatchSnapshot(); }); @@ -529,8 +498,7 @@ test("can add file in preflight", async () => { }); test("Given a non public bucket when reaching to a key public url it should throw an error", async () => { - //GIVEN - let error; + // GIVEN const app = new SimApp(); cloud.Bucket._newBucket(app, "my_bucket"); @@ -539,23 +507,15 @@ test("Given a non public bucket when reaching to a key public url it should thro const KEY = "KEY"; - // WHEN - try { - await client.publicUrl(KEY); - } catch (err) { - error = err; - } - // THEN - expect(error?.message).toBe( - "Cannot provide public url for a non-public bucket" + await expect(() => client.publicUrl(KEY)).rejects.toThrowError( + /Cannot provide public url for a non-public bucket/ ); await s.stop(); }); test("Given a public bucket when reaching to a non existent key, public url it should throw an error", async () => { - //GIVEN - let error; + // GIVEN const app = new SimApp(); cloud.Bucket._newBucket(app, "my_bucket", { public: true }); @@ -564,21 +524,14 @@ test("Given a public bucket when reaching to a non existent key, public url it s const KEY = "KEY"; - // WHEN - try { - await client.publicUrl(KEY); - } catch (err) { - error = err; - } - - expect(error?.message).toBe( - "Cannot provide public url for an non-existent key (key=KEY)" - ); // THEN + await expect(() => client.publicUrl(KEY)).rejects.toThrowError( + /Cannot provide public url for an non-existent key/ + ); await s.stop(); }); -test("Given a public bucket, when giving one of its keys, we should get it's public url", async () => { +test("Given a public bucket, when giving one of its keys, we should get its public url", async () => { // GIVEN const app = new SimApp(); cloud.Bucket._newBucket(app, "my_bucket", { public: true }); @@ -595,8 +548,8 @@ test("Given a public bucket, when giving one of its keys, we should get it's pub // THEN await s.stop(); - const filePath = `${client.fileDir}/${KEY}`; - expect(response).toEqual(url.pathToFileURL(filePath).href); + // file paths are different on windows and linux + expect(response.endsWith("KEY")).toBe(true); }); test("check if an object exists in the bucket", async () => { diff --git a/libs/wingsdk/test/target-sim/file-counter.test.ts b/libs/wingsdk/test/target-sim/file-counter.test.ts index ba5e41a68b4..59b48c35032 100644 --- a/libs/wingsdk/test/target-sim/file-counter.test.ts +++ b/libs/wingsdk/test/target-sim/file-counter.test.ts @@ -6,64 +6,68 @@ import { Testing } from "../../src/simulator"; import { IResource, Trace } from "../../src/std"; import { SimApp } from "../sim-app"; -test("can create sequential files in a bucket", async () => { - // GIVEN - class HelloWorld extends Construct { - public readonly processor: IResource; +test( + "can create sequential files in a bucket", + async () => { + // GIVEN + class HelloWorld extends Construct { + public readonly processor: IResource; - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string) { + super(scope, id); - const counter = cloud.Counter._newCounter(this, "Counter", { - initial: 1000, - }); - const bucket = cloud.Bucket._newBucket(this, "Bucket"); - const queue = cloud.Queue._newQueue(this, "Queue"); - const processor = Testing.makeHandler( - this, - "Processor", - `async handle(event) { + const counter = cloud.Counter._newCounter(this, "Counter", { + initial: 1000, + }); + const bucket = cloud.Bucket._newBucket(this, "Bucket"); + const queue = cloud.Queue._newQueue(this, "Queue"); + const processor = Testing.makeHandler( + this, + "Processor", + `async handle(event) { let next = await this.counter.inc(); let key = "file-" + next + ".txt"; await this.bucket.put(key, event); }`, - { - counter: { - obj: counter, - ops: [cloud.CounterInflightMethods.INC], - }, - bucket: { - obj: bucket, - ops: [cloud.BucketInflightMethods.PUT], - }, - } - ); - this.processor = queue.setConsumer(processor); + { + counter: { + obj: counter, + ops: [cloud.CounterInflightMethods.INC], + }, + bucket: { + obj: bucket, + ops: [cloud.BucketInflightMethods.PUT], + }, + } + ); + this.processor = queue.setConsumer(processor); + } } - } - const app = new SimApp(); - const helloWorld = new HelloWorld(app, "HelloWorld"); + const app = new SimApp(); + const helloWorld = new HelloWorld(app, "HelloWorld"); - const s = await app.startSimulator(); + const s = await app.startSimulator(); - const pusher = s.getResource("/HelloWorld/Queue") as cloud.IQueueClient; + const pusher = s.getResource("/HelloWorld/Queue") as cloud.IQueueClient; - // WHEN - const traceCheck = (trace: Trace) => - trace.sourcePath === helloWorld.processor.node.path && - trace.data.status === "success"; + // WHEN + const traceCheck = (trace: Trace) => + trace.sourcePath === helloWorld.processor.node.path && + trace.data.status === "success"; - await pusher.push("kachow!"); - await waitUntilTraceCount(s, 1, traceCheck); - await pusher.push("zoom!"); - await waitUntilTraceCount(s, 2, traceCheck); + await pusher.push("kachow!"); + await waitUntilTraceCount(s, 1, traceCheck); + await pusher.push("zoom!"); + await waitUntilTraceCount(s, 2, traceCheck); - // THEN - const bucket = s.getResource("/HelloWorld/Bucket") as cloud.IBucketClient; - await expect(bucket.get("file-1000.txt")).resolves.toEqual("kachow!"); - await expect(bucket.get("file-1001.txt")).resolves.toEqual("zoom!"); - await s.stop(); + // THEN + const bucket = s.getResource("/HelloWorld/Bucket") as cloud.IBucketClient; + await expect(bucket.get("file-1000.txt")).resolves.toEqual("kachow!"); + await expect(bucket.get("file-1001.txt")).resolves.toEqual("zoom!"); + await s.stop(); - expect(app.snapshot()).toMatchSnapshot(); -}); + expect(app.snapshot()).toMatchSnapshot(); + }, + { timeout: 20000 } +); diff --git a/libs/wingsdk/test/target-sim/function.test.ts b/libs/wingsdk/test/target-sim/function.test.ts index fef87446d21..2468d26d5d2 100644 --- a/libs/wingsdk/test/target-sim/function.test.ts +++ b/libs/wingsdk/test/target-sim/function.test.ts @@ -210,12 +210,12 @@ test("runtime environment tests", async () => { // check that fetch is a function (we can't really make network calls here) const fetchFn = app.newCloudFunction(` - return fetch; + return typeof fetch; `); const cryptoFn = app.newCloudFunction(` const c = require("crypto"); - return c.createHash; + return typeof c.createHash; `); // check that we can import ESM modules @@ -226,9 +226,9 @@ test("runtime environment tests", async () => { // THEN const s = await app.startSimulator(); - expect(await cryptoFn(s)).toBeTypeOf("function"); + expect(await cryptoFn(s)).toEqual("function"); expect(await urlSearchParamsFn(s)).toBe("api"); - expect(await fetchFn(s)).toBeTypeOf("function"); + expect(await fetchFn(s)).toEqual("function"); expect(await esmModulesFn(s)).toHaveLength(21); await s.stop(); diff --git a/libs/wingsdk/test/target-sim/immutable-capture.test.ts b/libs/wingsdk/test/target-sim/immutable-capture.test.ts index dcce10c8a1f..27f5ae13f13 100644 --- a/libs/wingsdk/test/target-sim/immutable-capture.test.ts +++ b/libs/wingsdk/test/target-sim/immutable-capture.test.ts @@ -2,7 +2,7 @@ import { Construct } from "constructs"; import { test, expect } from "vitest"; import { Bucket } from "../../src/cloud"; import { Function, IFunctionClient } from "../../src/cloud/function"; -import { InflightBindings, NodeJsCode } from "../../src/core/inflight"; +import { InflightBindings } from "../../src/core/inflight"; import { Testing } from "../../src/simulator"; import { Duration } from "../../src/std"; import { SimApp } from "../sim-app"; diff --git a/libs/wingsdk/test/target-sim/queue.test.ts b/libs/wingsdk/test/target-sim/queue.test.ts index ee930800981..2644854f2bb 100644 --- a/libs/wingsdk/test/target-sim/queue.test.ts +++ b/libs/wingsdk/test/target-sim/queue.test.ts @@ -111,7 +111,7 @@ test("queue batch size of 2, purge the queue", async () => { expect(app.snapshot()).toMatchSnapshot(); }); -test("queue with one subscriber, batch size of 5", async () => { +test("queue with one subscriber, batch size of 5, should get processed in two batches", async () => { // GIVEN const app = new SimApp(); @@ -125,12 +125,7 @@ test("queue with one subscriber, batch size of 5", async () => { "OnDeployHandler", `\ async handle() { - await this.queue.push("A"); - await this.queue.push("B"); - await this.queue.push("C"); - await this.queue.push("D"); - await this.queue.push("E"); - await this.queue.push("F"); + await this.queue.push("A", "B", "C", "D", "E", "F"); }`, { queue: { diff --git a/libs/wingsdk/test/target-sim/redis.test.ts b/libs/wingsdk/test/target-sim/redis.test.ts index 8b9debff46b..2588bcf2904 100644 --- a/libs/wingsdk/test/target-sim/redis.test.ts +++ b/libs/wingsdk/test/target-sim/redis.test.ts @@ -1,4 +1,4 @@ -import { Redis as IoRedis } from "ioredis"; +// import { Redis as IoRedis } from "ioredis"; import { test, expect } from "vitest"; import * as ex from "../../src/ex"; import { SimApp } from "../sim-app"; @@ -22,21 +22,6 @@ test("create a Redis resource", async () => { expect(app.snapshot()).toMatchSnapshot(); }); -test("access a Redis resource", async () => { - // GIVEN - const app = new SimApp(); - ex.Redis._newRedis(app, "my_redis"); - - // THEN - await app._withSimulator(async (s) => { - const client = s.getResource("/my_redis") as ex.IRedisClient; - expect((await client.url()).startsWith("redis://")).toBeTruthy(); - const redisClient = (await client.rawClient()) as IoRedis; - await redisClient.set("foo", "bar"); - expect(await redisClient.get("foo")).toEqual("bar"); - }); -}); - test("can set and get a value", async () => { // GIVEN const app = new SimApp(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e07f59a972..f43599b2d7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1238,6 +1238,9 @@ importers: safe-stable-stringify: specifier: ^2.4.3 version: 2.4.3 + undici: + specifier: ^5.26.3 + version: 5.26.3 uuid: specifier: ^8.3.2 version: 8.3.2 @@ -4887,6 +4890,11 @@ packages: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true + /@fastify/busboy@2.0.0: + resolution: {integrity: sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==} + engines: {node: '>=14'} + dev: false + /@floating-ui/core@1.4.1: resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} dependencies: @@ -13275,7 +13283,7 @@ packages: dependencies: semver: 7.5.4 shelljs: 0.8.5 - typescript: 5.3.0-dev.20231012 + typescript: 5.3.0-dev.20231013 dev: true /dset@3.1.2: @@ -22712,8 +22720,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.3.0-dev.20231012: - resolution: {integrity: sha512-k/m+QK/2eVsPMAao4I28Cv2Y9WzUKFVhRqvFGZ3CIXc0lrMkw/1pSA+8tp7IMw1ecxoa1myFql1tmCUrMKCbzA==} + /typescript@5.3.0-dev.20231013: + resolution: {integrity: sha512-ZhuwwWpJ9Hec2JJaS0yOJOjkLDu6lm/PXjniirFFcFBArD5HA3K8P31XxPmwonBhk7oznUbhdIDu8LEYha9Kxw==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -22749,6 +22757,13 @@ packages: resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} dev: true + /undici@5.26.3: + resolution: {integrity: sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.0.0 + dev: false + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/delete.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/delete.test.w_compile_tf-aws.md index e25e9e02388..e7c7b05a0e9 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/delete.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/delete.test.w_compile_tf-aws.md @@ -18,7 +18,7 @@ module.exports = function({ $b }) { } catch ($error_actual) { const actual = $error_actual.message; - {((cond) => {if (!cond) throw new Error("assertion failed: actual == expected")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(actual,expected)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: actual.contains(expected)")})(actual.includes(expected))}; error = true; } {((cond) => {if (!cond) throw new Error("assertion failed: error")})(error)}; diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/metadata.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/metadata.test.w_compile_tf-aws.md index 27820f5d32f..30db872bae4 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/metadata.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/metadata.test.w_compile_tf-aws.md @@ -12,15 +12,16 @@ module.exports = function({ $b }) { } async handle() { (await $b.put("test1.txt","Foo")); - {((cond) => {if (!cond) throw new Error("assertion failed: b.metadata(\"test1.txt\").size == 3")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $b.metadata("test1.txt")).size,3)))}; - {((cond) => {if (!cond) throw new Error("assertion failed: b.metadata(\"test1.txt\").contentType == \"application/octet-stream\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $b.metadata("test1.txt")).contentType,"application/octet-stream")))}; - {((cond) => {if (!cond) throw new Error("assertion failed: b.metadata(\"test1.txt\").lastModified.year >= 2023")})(((await $b.metadata("test1.txt")).lastModified.year >= 2023))}; + const metadata = (await $b.metadata("test1.txt")); + {((cond) => {if (!cond) throw new Error("assertion failed: metadata.size == 3")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(metadata.size,3)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: metadata.contentType == \"application/octet-stream\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(metadata.contentType,"application/octet-stream")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: metadata.lastModified.year >= 2023")})((metadata.lastModified.year >= 2023))}; try { (await $b.metadata("no-such-file.txt")).lastModified; } catch ($error_e) { const e = $error_e.message; - {((cond) => {if (!cond) throw new Error("assertion failed: e == \"Object does not exist (key=no-such-file.txt).\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(e,"Object does not exist (key=no-such-file.txt).")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: e.contains(\"Object does not exist (key=no-such-file.txt).\")")})(e.includes("Object does not exist (key=no-such-file.txt)."))}; } } } diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/public_url.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/public_url.test.w_compile_tf-aws.md index 492aaa58a31..1d15d4d8a02 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/public_url.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/bucket/public_url.test.w_compile_tf-aws.md @@ -18,7 +18,7 @@ module.exports = function({ $http_Util, $privateBucket, $publicBucket, $util_Uti } catch ($error_actual) { const actual = $error_actual.message; - {((cond) => {if (!cond) throw new Error("assertion failed: actual == expected")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(actual,expected)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: actual.contains(expected)")})(actual.includes(expected))}; error = true; } {((cond) => {if (!cond) throw new Error("assertion failed: error")})(error)}; diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/push.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/push.test.w_compile_tf-aws.md index ba2c5a49154..c4ecc0c8587 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/push.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/queue/push.test.w_compile_tf-aws.md @@ -18,7 +18,7 @@ module.exports = function({ $q, $std_Duration, $util_Util }) { } catch ($error_e) { const e = $error_e.message; - {((cond) => {if (!cond) throw new Error("assertion failed: e == \"Empty messages are not allowed\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(e,"Empty messages are not allowed")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: e.contains(\"Empty messages are not allowed\")")})(e.includes("Empty messages are not allowed"))}; } try { (await $q.push("Foo","")); @@ -26,7 +26,7 @@ module.exports = function({ $q, $std_Duration, $util_Util }) { } catch ($error_e) { const e = $error_e.message; - {((cond) => {if (!cond) throw new Error("assertion failed: e == \"Empty messages are not allowed\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(e,"Empty messages are not allowed")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: e.contains(\"Empty messages are not allowed\")")})(e.includes("Empty messages are not allowed"))}; } (await $q.push("Foo")); {((cond) => {if (!cond) throw new Error("assertion failed: util.waitUntil((): bool => {\n return q.approxSize() == 1;\n })")})((await $util_Util.waitUntil(async () => { diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/schedule/on_tick.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/schedule/on_tick.test.w_compile_tf-aws.md index 8bafbad135f..e179fcfca09 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/schedule/on_tick.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/schedule/on_tick.test.w_compile_tf-aws.md @@ -49,11 +49,11 @@ module.exports = function({ $c1, $c2, $std_Duration, $util_Util }) { return $obj; } async handle() { - {((cond) => {if (!cond) throw new Error("assertion failed: c1.peek() == 0")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $c1.peek()),0)))}; - {((cond) => {if (!cond) throw new Error("assertion failed: c2.peek() == 0")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $c2.peek()),0)))}; + const c1val = (await $c1.peek()); + const c2val = (await $c2.peek()); (await $util_Util.sleep((await $std_Duration.fromSeconds(66)))); - {((cond) => {if (!cond) throw new Error("assertion failed: c1.peek() >= 1")})(((await $c1.peek()) >= 1))}; - {((cond) => {if (!cond) throw new Error("assertion failed: c2.peek() >= 1")})(((await $c2.peek()) >= 1))}; + {((cond) => {if (!cond) throw new Error("assertion failed: c1.peek() >= c1val + 1")})(((await $c1.peek()) >= (c1val + 1)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: c2.peek() >= c2val + 1")})(((await $c2.peek()) >= (c2val + 1)))}; } } return $Closure3; @@ -508,7 +508,7 @@ class $Root extends $stdlib.std.Resource { const c2 = this.node.root.newAbstract("@winglang/sdk.cloud.Counter",this,"c2"); (from_cron.onTick(new $Closure1(this,"$Closure1"))); (from_rate.onTick(new $Closure2(this,"$Closure2"))); - this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"on tick is called both for rate and cron schedules",new $Closure3(this,"$Closure3"),({"timeout": (std.Duration.fromSeconds(120))})); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"on tick is called both for rate and cron schedules",new $Closure3(this,"$Closure3"),{ timeout: (std.Duration.fromSeconds(120)) }); } } const $App = $stdlib.core.App.for(process.env.WING_TARGET); diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/table/get.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/table/get.test.w_compile_tf-aws.md index 1417ffc8f1e..646e9724e18 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/table/get.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/table/get.test.w_compile_tf-aws.md @@ -23,7 +23,7 @@ module.exports = function({ $table }) { } catch ($error_actual) { const actual = $error_actual.message; - {((cond) => {if (!cond) throw new Error("assertion failed: actual == expected")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(actual,expected)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: actual.contains(expected)")})(actual.includes(expected))}; error = true; } {((cond) => {if (!cond) throw new Error("assertion failed: error")})(error)}; diff --git a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md index a6af688d70b..6dc9ca346c8 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/redis.test.w_compile_tf-aws.md @@ -30,10 +30,6 @@ module.exports = function({ $queue, $r, $r2, $util_Util }) { return $obj; } async handle() { - const connection = (await $r.rawClient()); - (await connection.set("wing","does redis")); - const value = (await connection.get("wing")); - {((cond) => {if (!cond) throw new Error("assertion failed: value == \"does redis\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(value,"does redis")))}; (await $r2.set("wing","does redis again")); const value2 = (await $r2.get("wing")); {((cond) => {if (!cond) throw new Error("assertion failed: value2 == \"does redis again\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(value2,"does redis again")))}; @@ -623,7 +619,7 @@ class $Root extends $stdlib.std.Resource { _registerBind(host, ops) { if (ops.includes("handle")) { $Closure2._registerBindObject(queue, host, ["push"]); - $Closure2._registerBindObject(r, host, ["get", "rawClient"]); + $Closure2._registerBindObject(r, host, ["get"]); $Closure2._registerBindObject(r2, host, ["get", "set"]); } super._registerBind(host, ops);