From ecb5bc89945457d3f63eaf907b747c39f09b87f7 Mon Sep 17 00:00:00 2001 From: Tsuf Cohen <39455181+tsuf239@users.noreply.github.com> Date: Tue, 15 Aug 2023 12:52:29 +0300 Subject: [PATCH] fix(sdk): changing redis empty value to be nil (#3810) fixes [#3493](https://github.com/winglang/wing/issues/3493) Changing Redis empty value to be nil- before it was js' null, which doesn't have an equivalent in wing, therefore could not be used ## 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 - [x] 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)*. --- examples/tests/valid/redis.w | 18 +- libs/wingsdk/src/ex/redis.ts | 2 +- libs/wingsdk/test/target-sim/redis.test.ts | 4 +- .../valid/redis.w_compile_tf-aws.md | 215 +++++++++++++++--- .../test_corpus/valid/redis.w_test_sim.md | 2 +- 5 files changed, 207 insertions(+), 34 deletions(-) diff --git a/examples/tests/valid/redis.w b/examples/tests/valid/redis.w index e37ccd4293d..93f8378eeff 100644 --- a/examples/tests/valid/redis.w +++ b/examples/tests/valid/redis.w @@ -5,12 +5,19 @@ skipPlatforms: \*/ bring cloud; +bring util; bring ex; let r = new ex.Redis(); let r2 = new ex.Redis() as "r2"; -test "test" { +let queue = new cloud.Queue(); + +queue.setConsumer(inflight (message: str) => { + r.set("hello", message); +}, timeout: 3s); + +test "testing Redis" { // Using raw client let connection = r.rawClient(); connection.set("wing", "does redis"); @@ -21,4 +28,13 @@ test "test" { r2.set("wing", "does redis again"); let value2 = r2.get("wing"); assert(value2 == "does redis again"); + + //With waitUntil + queue.push("world!"); + + util.waitUntil((): bool => { + return r.get("hello") != nil; + }); + + assert("world!" == "${r.get("hello")}"); } diff --git a/libs/wingsdk/src/ex/redis.ts b/libs/wingsdk/src/ex/redis.ts index 2404cbf405c..c3068184cc3 100644 --- a/libs/wingsdk/src/ex/redis.ts +++ b/libs/wingsdk/src/ex/redis.ts @@ -157,7 +157,7 @@ export abstract class RedisClientBase implements IRedisClient { public async get(key: string): Promise { let redis = await this.rawClient(); - let result = await redis.get(key); + let result = (await redis.get(key)) ?? undefined; // for wing to return nil return result; } diff --git a/libs/wingsdk/test/target-sim/redis.test.ts b/libs/wingsdk/test/target-sim/redis.test.ts index c4cadcb56e0..8b9debff46b 100644 --- a/libs/wingsdk/test/target-sim/redis.test.ts +++ b/libs/wingsdk/test/target-sim/redis.test.ts @@ -102,7 +102,7 @@ test("can del a value", async () => { const recordsDeleted = await client.del(key); const value = await client.get(key); expect(recordsDeleted).toEqual(1); - expect(value).toEqual(null); + expect(value).toBeUndefined(); }); }); @@ -130,6 +130,6 @@ test("get a value that does not exist", async () => { await app._withSimulator(async (s) => { const client = s.getResource("/my_redis") as ex.IRedisClient; const value = await client.get(key); - expect(value).toEqual(null); + expect(value).toBeUndefined(); }); }); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/redis.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/redis.w_compile_tf-aws.md index 645e2bd377b..451cba7d702 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/redis.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/redis.w_compile_tf-aws.md @@ -2,8 +2,26 @@ ## inflight.$Closure1-1.js ```js -module.exports = function({ $r, $r2 }) { +module.exports = function({ $r }) { class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle(message) { + (await $r.set("hello",message)); + } + } + return $Closure1; +} + +``` + +## inflight.$Closure2-1.js +```js +module.exports = function({ $queue, $r, $r2, $util_Util }) { + class $Closure2 { constructor({ }) { const $obj = (...args) => this.handle(...args); Object.setPrototypeOf($obj, this); @@ -17,9 +35,15 @@ module.exports = function({ $r, $r2 }) { (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")))}; + (await $queue.push("world!")); + (await $util_Util.waitUntil(async () => { + return ((await $r.get("hello")) !== undefined); + } + )); + {((cond) => {if (!cond) throw new Error("assertion failed: \"world!\" == \"${r.get(\"hello\")}\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })("world!",String.raw({ raw: ["", ""] }, (await $r.get("hello"))))))}; } } - return $Closure1; + return $Closure2; } ``` @@ -45,7 +69,7 @@ module.exports = function({ $r, $r2 }) { }, "output": { "WING_TEST_RUNNER_FUNCTION_ARNS": { - "value": "[[\"root/Default/Default/test:test\",\"${aws_lambda_function.testtest_Handler_295107CC.arn}\"]]" + "value": "[[\"root/Default/Default/test:testing Redis\",\"${aws_lambda_function.testtestingRedis_Handler_7678DD27.arn}\"]]" } }, "provider": { @@ -131,38 +155,67 @@ module.exports = function({ $r, $r2 }) { } }, "aws_iam_role": { - "testtest_Handler_IamRole_15693C93": { + "cloudQueue-SetConsumer-cdafee6e_IamRole_2548D828": { "//": { "metadata": { - "path": "root/Default/Default/test:test/Handler/IamRole", - "uniqueId": "testtest_Handler_IamRole_15693C93" + "path": "root/Default/Default/cloud.Queue-SetConsumer-cdafee6e/IamRole", + "uniqueId": "cloudQueue-SetConsumer-cdafee6e_IamRole_2548D828" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + }, + "testtestingRedis_Handler_IamRole_8B9140DE": { + "//": { + "metadata": { + "path": "root/Default/Default/test:testing Redis/Handler/IamRole", + "uniqueId": "testtestingRedis_Handler_IamRole_8B9140DE" } }, "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" } }, "aws_iam_role_policy": { - "testtest_Handler_IamRolePolicy_AF0279BD": { + "cloudQueue-SetConsumer-cdafee6e_IamRolePolicy_37133937": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Queue-SetConsumer-cdafee6e/IamRolePolicy", + "uniqueId": "cloudQueue-SetConsumer-cdafee6e_IamRolePolicy_37133937" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"sqs:ReceiveMessage\",\"sqs:ChangeMessageVisibility\",\"sqs:GetQueueUrl\",\"sqs:DeleteMessage\",\"sqs:GetQueueAttributes\"],\"Resource\":[\"${aws_sqs_queue.cloudQueue.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"elasticache:Describe*\"],\"Resource\":[\"${aws_elasticache_cluster.exRedis_RedisCluster_3C9A5882.arn}\"],\"Effect\":\"Allow\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.cloudQueue-SetConsumer-cdafee6e_IamRole_2548D828.name}" + }, + "testtestingRedis_Handler_IamRolePolicy_21FBAD46": { "//": { "metadata": { - "path": "root/Default/Default/test:test/Handler/IamRolePolicy", - "uniqueId": "testtest_Handler_IamRolePolicy_AF0279BD" + "path": "root/Default/Default/test:testing Redis/Handler/IamRolePolicy", + "uniqueId": "testtestingRedis_Handler_IamRolePolicy_21FBAD46" } }, - "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"elasticache:Describe*\"],\"Resource\":[\"${aws_elasticache_cluster.exRedis_RedisCluster_3C9A5882.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"elasticache:Describe*\"],\"Resource\":[\"${aws_elasticache_cluster.r2_RedisCluster_C6087F40.arn}\"],\"Effect\":\"Allow\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"}]}", - "role": "${aws_iam_role.testtest_Handler_IamRole_15693C93.name}" + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"elasticache:Describe*\"],\"Resource\":[\"${aws_elasticache_cluster.exRedis_RedisCluster_3C9A5882.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"elasticache:Describe*\"],\"Resource\":[\"${aws_elasticache_cluster.r2_RedisCluster_C6087F40.arn}\"],\"Effect\":\"Allow\"},{\"Action\":[\"sqs:SendMessage\"],\"Resource\":[\"${aws_sqs_queue.cloudQueue.arn}\"],\"Effect\":\"Allow\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"},{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateNetworkInterface\",\"ec2:DescribeNetworkInterfaces\",\"ec2:DeleteNetworkInterface\",\"ec2:DescribeSubnets\",\"ec2:DescribeSecurityGroups\"],\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.testtestingRedis_Handler_IamRole_8B9140DE.name}" } }, "aws_iam_role_policy_attachment": { - "testtest_Handler_IamRolePolicyAttachment_ADF4752D": { + "cloudQueue-SetConsumer-cdafee6e_IamRolePolicyAttachment_45079F65": { "//": { "metadata": { - "path": "root/Default/Default/test:test/Handler/IamRolePolicyAttachment", - "uniqueId": "testtest_Handler_IamRolePolicyAttachment_ADF4752D" + "path": "root/Default/Default/cloud.Queue-SetConsumer-cdafee6e/IamRolePolicyAttachment", + "uniqueId": "cloudQueue-SetConsumer-cdafee6e_IamRolePolicyAttachment_45079F65" } }, "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "role": "${aws_iam_role.testtest_Handler_IamRole_15693C93.name}" + "role": "${aws_iam_role.cloudQueue-SetConsumer-cdafee6e_IamRole_2548D828.name}" + }, + "testtestingRedis_Handler_IamRolePolicyAttachment_4A5E3F4E": { + "//": { + "metadata": { + "path": "root/Default/Default/test:testing Redis/Handler/IamRolePolicyAttachment", + "uniqueId": "testtestingRedis_Handler_IamRolePolicyAttachment_4A5E3F4E" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.testtestingRedis_Handler_IamRole_8B9140DE.name}" } }, "aws_internet_gateway": { @@ -179,29 +232,74 @@ module.exports = function({ $r, $r2 }) { "vpc_id": "${aws_vpc.VPC.id}" } }, + "aws_lambda_event_source_mapping": { + "cloudQueue_EventSourceMapping_41814136": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Queue/EventSourceMapping", + "uniqueId": "cloudQueue_EventSourceMapping_41814136" + } + }, + "batch_size": 1, + "event_source_arn": "${aws_sqs_queue.cloudQueue.arn}", + "function_name": "${aws_lambda_function.cloudQueue-SetConsumer-cdafee6e.function_name}" + } + }, "aws_lambda_function": { - "testtest_Handler_295107CC": { + "cloudQueue-SetConsumer-cdafee6e": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Queue-SetConsumer-cdafee6e/Default", + "uniqueId": "cloudQueue-SetConsumer-cdafee6e" + } + }, + "environment": { + "variables": { + "REDIS_CLUSTER_ID_89baf91f": "${aws_elasticache_cluster.exRedis_RedisCluster_3C9A5882.cluster_id}", + "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.cloudQueue-SetConsumer-cdafee6e_IamRole_2548D828.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.Code.bucket}", + "s3_key": "${aws_s3_object.cloudQueue-SetConsumer-cdafee6e_S3Object_8868B9FB.key}", + "timeout": 3, + "vpc_config": { + "security_group_ids": [ + "${aws_security_group.exRedis_securityGroup_3948C3F2.id}" + ], + "subnet_ids": [ + "${aws_subnet.PrivateSubnet.id}" + ] + } + }, + "testtestingRedis_Handler_7678DD27": { "//": { "metadata": { - "path": "root/Default/Default/test:test/Handler/Default", - "uniqueId": "testtest_Handler_295107CC" + "path": "root/Default/Default/test:testing Redis/Handler/Default", + "uniqueId": "testtestingRedis_Handler_7678DD27" } }, "environment": { "variables": { + "QUEUE_URL_31e95cbd": "${aws_sqs_queue.cloudQueue.url}", "REDIS_CLUSTER_ID_30c8c4ae": "${aws_elasticache_cluster.r2_RedisCluster_C6087F40.cluster_id}", "REDIS_CLUSTER_ID_89baf91f": "${aws_elasticache_cluster.exRedis_RedisCluster_3C9A5882.cluster_id}", - "WING_FUNCTION_NAME": "Handler-c8f4f2a1", + "WING_FUNCTION_NAME": "Handler-c8775e77", "WING_TARGET": "tf-aws" } }, - "function_name": "Handler-c8f4f2a1", + "function_name": "Handler-c8775e77", "handler": "index.handler", "publish": true, - "role": "${aws_iam_role.testtest_Handler_IamRole_15693C93.arn}", + "role": "${aws_iam_role.testtestingRedis_Handler_IamRole_8B9140DE.arn}", "runtime": "nodejs18.x", "s3_bucket": "${aws_s3_bucket.Code.bucket}", - "s3_key": "${aws_s3_object.testtest_Handler_S3Object_9F4E28A7.key}", + "s3_key": "${aws_s3_object.testtestingRedis_Handler_S3Object_3AE6E27A.key}", "timeout": 30, "vpc_config": { "security_group_ids": [ @@ -326,11 +424,22 @@ module.exports = function({ $r, $r2 }) { } }, "aws_s3_object": { - "testtest_Handler_S3Object_9F4E28A7": { + "cloudQueue-SetConsumer-cdafee6e_S3Object_8868B9FB": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Queue-SetConsumer-cdafee6e/S3Object", + "uniqueId": "cloudQueue-SetConsumer-cdafee6e_S3Object_8868B9FB" + } + }, + "bucket": "${aws_s3_bucket.Code.bucket}", + "key": "", + "source": "" + }, + "testtestingRedis_Handler_S3Object_3AE6E27A": { "//": { "metadata": { - "path": "root/Default/Default/test:test/Handler/S3Object", - "uniqueId": "testtest_Handler_S3Object_9F4E28A7" + "path": "root/Default/Default/test:testing Redis/Handler/S3Object", + "uniqueId": "testtestingRedis_Handler_S3Object_3AE6E27A" } }, "bucket": "${aws_s3_bucket.Code.bucket}", @@ -420,6 +529,17 @@ module.exports = function({ $r, $r2 }) { "vpc_id": "${aws_vpc.VPC.id}" } }, + "aws_sqs_queue": { + "cloudQueue": { + "//": { + "metadata": { + "path": "root/Default/Default/cloud.Queue/Default", + "uniqueId": "cloudQueue" + } + }, + "name": "cloud-Queue-c86e03d8" + } + }, "aws_subnet": { "PrivateSubnet": { "//": { @@ -475,6 +595,7 @@ const $outdir = process.env.WING_SYNTH_DIR ?? "."; const $wing_is_test = process.env.WING_IS_TEST === "true"; const std = $stdlib.std; const cloud = $stdlib.cloud; +const util = $stdlib.util; const ex = $stdlib.ex; class $Root extends $stdlib.std.Resource { constructor(scope, id) { @@ -489,7 +610,6 @@ class $Root extends $stdlib.std.Resource { return $stdlib.core.NodeJsCode.fromInline(` require("./inflight.$Closure1-1.js")({ $r: ${context._lift(r)}, - $r2: ${context._lift(r2)}, }) `); } @@ -506,15 +626,52 @@ class $Root extends $stdlib.std.Resource { } _registerBind(host, ops) { if (ops.includes("handle")) { - $Closure1._registerBindObject(r, host, ["rawClient"]); - $Closure1._registerBindObject(r2, host, ["get", "set"]); + $Closure1._registerBindObject(r, host, ["set"]); + } + super._registerBind(host, ops); + } + } + class $Closure2 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this._addInflightOps("handle", "$inflight_init"); + this.display.hidden = true; + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.$Closure2-1.js")({ + $queue: ${context._lift(queue)}, + $r: ${context._lift(r)}, + $r2: ${context._lift(r2)}, + $util_Util: ${context._lift(util.Util)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure2Client = ${$Closure2._toInflightType(this).text}; + const client = new $Closure2Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("handle")) { + $Closure2._registerBindObject(queue, host, ["push"]); + $Closure2._registerBindObject(r, host, ["get", "rawClient"]); + $Closure2._registerBindObject(r2, host, ["get", "set"]); } super._registerBind(host, ops); } } const r = this.node.root.newAbstract("@winglang/sdk.ex.Redis",this,"ex.Redis"); const r2 = this.node.root.newAbstract("@winglang/sdk.ex.Redis",this,"r2"); - this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:test",new $Closure1(this,"$Closure1")); + const queue = this.node.root.newAbstract("@winglang/sdk.cloud.Queue",this,"cloud.Queue"); + (queue.setConsumer(new $Closure1(this,"$Closure1"),{ timeout: (std.Duration.fromSeconds(3)) })); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:testing Redis",new $Closure2(this,"$Closure2")); } } const $App = $stdlib.core.App.for(process.env.WING_TARGET); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/redis.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/redis.w_test_sim.md index fae9f3c9e52..a3c13ee3557 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/redis.w_test_sim.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/redis.w_test_sim.md @@ -2,7 +2,7 @@ ## stdout.log ```log -pass ─ redis.wsim » root/env0/test:test +pass ─ redis.wsim » root/env0/test:testing Redis Tests 1 passed (1)