From 4e5ccfeecb0e9560ac79de539e6d39f5eb4a9c0b Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Thu, 4 Jan 2024 17:07:19 +1100 Subject: [PATCH] Port remaining tests and fix bugs --- ndc-lambda-sdk/.mocharc.json | 3 +- ndc-lambda-sdk/package-lock.json | 73 ++- ndc-lambda-sdk/package.json | 4 +- ndc-lambda-sdk/src/execution.ts | 2 +- ndc-lambda-sdk/src/inference.ts | 14 +- .../test/execution/prepare-arguments.test.ts | 311 +++++++++++ .../basic-inference/basic-inference.test.ts | 519 +++++++++++++++++- .../basic-inference/complex-types.ts | 42 ++ .../basic-inference/nullable-types.ts | 18 + ...x-return-type.ts => object-return-type.ts} | 0 .../basic-inference/recursive-types.ts | 14 + .../inference/basic-inference/simple-types.ts | 5 +- .../external-dependencies.test.ts | 142 +++++ .../external-dependencies/use-npm-package.ts | 9 + .../use-postgres-package.ts | 112 ++++ .../conflict-from-import.dep.ts | 4 + .../naming-conflicts/conflict-from-import.ts | 16 + .../naming-conflicts/naming-conflicts.test.ts | 76 +++ .../inference/unsupported-types/classes.ts | 8 + .../inference/unsupported-types/promises.ts | 15 + .../unsupported-types.test.ts | 69 +++ .../test/inference/unsupported-types/void.ts | 2 + ndc-lambda-sdk/test/schema/ndc-schema.test.ts | 173 ++++++ 23 files changed, 1616 insertions(+), 15 deletions(-) create mode 100644 ndc-lambda-sdk/test/execution/prepare-arguments.test.ts create mode 100644 ndc-lambda-sdk/test/inference/basic-inference/complex-types.ts create mode 100644 ndc-lambda-sdk/test/inference/basic-inference/nullable-types.ts rename ndc-lambda-sdk/test/inference/basic-inference/{complex-return-type.ts => object-return-type.ts} (100%) create mode 100644 ndc-lambda-sdk/test/inference/basic-inference/recursive-types.ts create mode 100644 ndc-lambda-sdk/test/inference/external-dependencies/external-dependencies.test.ts create mode 100644 ndc-lambda-sdk/test/inference/external-dependencies/use-npm-package.ts create mode 100644 ndc-lambda-sdk/test/inference/external-dependencies/use-postgres-package.ts create mode 100644 ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.dep.ts create mode 100644 ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.ts create mode 100644 ndc-lambda-sdk/test/inference/naming-conflicts/naming-conflicts.test.ts create mode 100644 ndc-lambda-sdk/test/inference/unsupported-types/classes.ts create mode 100644 ndc-lambda-sdk/test/inference/unsupported-types/promises.ts create mode 100644 ndc-lambda-sdk/test/inference/unsupported-types/unsupported-types.test.ts create mode 100644 ndc-lambda-sdk/test/inference/unsupported-types/void.ts create mode 100644 ndc-lambda-sdk/test/schema/ndc-schema.test.ts diff --git a/ndc-lambda-sdk/.mocharc.json b/ndc-lambda-sdk/.mocharc.json index a7e9832..ee2e357 100644 --- a/ndc-lambda-sdk/.mocharc.json +++ b/ndc-lambda-sdk/.mocharc.json @@ -5,6 +5,7 @@ "test/**/*.test.ts" ], "watch-files": [ - "src" + "src", + "test" ] } diff --git a/ndc-lambda-sdk/package-lock.json b/ndc-lambda-sdk/package-lock.json index 5bd4378..ebe4c85 100644 --- a/ndc-lambda-sdk/package-lock.json +++ b/ndc-lambda-sdk/package-lock.json @@ -26,7 +26,9 @@ "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "chai": "^4.3.7", - "mocha": "^10.2.0" + "mocha": "^10.2.0", + "node-emoji": "^2.1.3", + "node-postgres": "^0.6.2" } }, "node_modules/@cspotcode/source-map-support": { @@ -107,6 +109,18 @@ "resolved": "https://registry.npmjs.org/@json-schema-tools/meta-schema/-/meta-schema-1.7.0.tgz", "integrity": "sha512-3pDzVUssW3hVnf8gvSu1sKaVIpLyvmpbxgGfkUoaBiErFKRS2CZOufHD0pUFoa5e6Cd5oa72s402nJbnDz76CA==" }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@swc/core": { "version": "1.3.100", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.100.tgz", @@ -656,6 +670,15 @@ "node": ">=8" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -837,6 +860,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1534,6 +1563,27 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-emoji": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", + "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-postgres": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/node-postgres/-/node-postgres-0.6.2.tgz", + "integrity": "sha512-wjaW+KutPOFemtBDuogyy6OMMOs2vpSOwuqbUhFUbrDEOTk66K2lmRyvOdO4t26qGtnPyZXQ4bAuF17cBhi5aQ==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1956,6 +2006,18 @@ "node": ">=8" } }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sonic-boom": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", @@ -2223,6 +2285,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/ndc-lambda-sdk/package.json b/ndc-lambda-sdk/package.json index bcafa26..1a059d8 100644 --- a/ndc-lambda-sdk/package.json +++ b/ndc-lambda-sdk/package.json @@ -43,6 +43,8 @@ "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "chai": "^4.3.7", - "mocha": "^10.2.0" + "mocha": "^10.2.0", + "node-emoji": "^2.1.3", + "node-postgres": "^0.6.2" } } diff --git a/ndc-lambda-sdk/src/execution.ts b/ndc-lambda-sdk/src/execution.ts index 8e827c2..8e6e79e 100644 --- a/ndc-lambda-sdk/src/execution.ts +++ b/ndc-lambda-sdk/src/execution.ts @@ -95,7 +95,7 @@ function resolveArgumentValues(args: Record, variableValue }); } -function prepareArguments(args: Record, functionDefinition: schema.FunctionDefinition, objectTypes: schema.ObjectTypeDefinitions): unknown[] { +export function prepareArguments(args: Record, functionDefinition: schema.FunctionDefinition, objectTypes: schema.ObjectTypeDefinitions): unknown[] { return functionDefinition.arguments.map(argDef => coerceArgumentValue(args[argDef.argumentName], argDef.type, [argDef.argumentName], objectTypes)); } diff --git a/ndc-lambda-sdk/src/inference.ts b/ndc-lambda-sdk/src/inference.ts index 75872e0..3c5d82b 100644 --- a/ndc-lambda-sdk/src/inference.ts +++ b/ndc-lambda-sdk/src/inference.ts @@ -300,7 +300,7 @@ function deriveSchemaTypeIfNullableType(tsType: ts.Type, typePath: TypePathSegme function deriveSchemaTypeIfObjectType(tsType: ts.Type, typePath: TypePathSegment[], context: TypeDerivationContext, recursionDepth: number): Result | undefined { const info = getObjectTypeInfo(tsType, typePath, context.typeChecker, context.functionsFilePath); if (info) { - // Shortcut recursion if the type has already been named + // Short-circuit recursion if the type has already been named if (context.objectTypeDefinitions[info.generatedTypeName]) { return new Ok({ typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings: [] }); } @@ -316,10 +316,14 @@ function deriveSchemaTypeIfObjectType(tsType: ts.Type, typePath: TypePathSegment }); }); - return propertyResults.map(properties => { - context.objectTypeDefinitions[info.generatedTypeName] = { properties } - return { typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings } - }) + if (propertyResults instanceof Ok) { + context.objectTypeDefinitions[info.generatedTypeName] = { properties: propertyResults.data } + return new Ok({ typeDefinition: { type: 'named', name: info.generatedTypeName, kind: "object" }, warnings }) + } else { + // Remove the recursion short-circuit to ensure errors are raised if this type is encountered again + delete context.objectTypeDefinitions[info.generatedTypeName]; + return new Err(propertyResults.error); + } } } diff --git a/ndc-lambda-sdk/test/execution/prepare-arguments.test.ts b/ndc-lambda-sdk/test/execution/prepare-arguments.test.ts new file mode 100644 index 0000000..b82fd03 --- /dev/null +++ b/ndc-lambda-sdk/test/execution/prepare-arguments.test.ts @@ -0,0 +1,311 @@ +import { describe, it } from "mocha"; +import { assert } from "chai"; +import { prepareArguments } from "../../src/execution"; +import { FunctionDefinition, FunctionNdcKind, NullOrUndefinability, ObjectTypeDefinitions } from "../../src/schema"; + +describe("prepare arguments", function() { + it("argument ordering", function() { + const functionDefinition: FunctionDefinition = { + ndcKind: FunctionNdcKind.Function, + description: null, + arguments: [ + { + argumentName: "c", + description: null, + type: { + type: "named", + kind: "scalar", + name: "Float" + } + }, + { + argumentName: "a", + description: null, + type: { + type: "named", + kind: "scalar", + name: "Float" + } + }, + { + argumentName: "b", + description: null, + type: { + type: "named", + kind: "scalar", + name: "Float" + } + }, + ], + resultType: { + type: "named", + kind: "scalar", + name: "String" + } + } + const objectTypes: ObjectTypeDefinitions = {} + const args = { + b: 1, + a: 2, + c: 3, + } + + const preparedArgs = prepareArguments(args, functionDefinition, objectTypes); + + assert.deepStrictEqual(preparedArgs, [ 3, 2, 1 ]); + }); + + describe("nullable type coercion", function() { + const functionDefinition: FunctionDefinition = { + ndcKind: FunctionNdcKind.Function, + description: null, + arguments: [ + { + argumentName: "nullOnlyArg", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + argumentName: "undefinedOnlyArg", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + argumentName: "nullOrUndefinedArg", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsEither, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + argumentName: "objectArg", + description: null, + type: { + type: "named", + kind: "object", + name: "MyObject", + } + }, + { + argumentName: "nullOnlyArrayArg", + description: null, + type: { + type: "array", + elementType: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + } + }, + { + argumentName: "undefinedOnlyArrayArg", + description: null, + type: { + type: "array", + elementType: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + } + }, + { + argumentName: "nullOrUndefinedArrayArg", + description: null, + type: { + type: "array", + elementType: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsEither, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + } + }, + ], + resultType: { + type: "named", + kind: "scalar", + name: "String" + } + } + const objectTypes: ObjectTypeDefinitions = { + "MyObject": { + properties: [ + { + propertyName: "nullOnlyProp", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + propertyName: "undefinedOnlyProp", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + propertyName: "nullOrUndefinedProp", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsEither, + underlyingType: { + type: "named", + kind: "scalar", + name: "String" + } + } + } + ] + } + } + const testCases = [ + { + name: "all nulls", + args: { + nullOnlyArg: null, + undefinedOnlyArg: null, + nullOrUndefinedArg: null, + objectArg: { + nullOnlyProp: null, + undefinedOnlyProp: null, + nullOrUndefinedProp: null, + }, + nullOnlyArrayArg: [null, null], + undefinedOnlyArrayArg: [null, null], + nullOrUndefinedArrayArg: [null, null], + }, + expected: [ + null, + undefined, + null, + { nullOnlyProp: null, undefinedOnlyProp: undefined, nullOrUndefinedProp: null }, + [null, null], + [undefined, undefined], + [null, null], + ] + }, + { + name: "all undefineds", + args: { + nullOnlyArg: undefined, + undefinedOnlyArg: undefined, + nullOrUndefinedArg: undefined, + objectArg: { + nullOnlyProp: undefined, + undefinedOnlyProp: undefined, + nullOrUndefinedProp: undefined, + }, + nullOnlyArrayArg: [undefined, undefined], + undefinedOnlyArrayArg: [undefined, undefined], + nullOrUndefinedArrayArg: [undefined, undefined], + }, + expected: [ + null, + undefined, + undefined, + { nullOnlyProp: null, undefinedOnlyProp: undefined, nullOrUndefinedProp: undefined }, + [null, null], + [undefined, undefined], + [undefined, undefined], + ] + }, + { + name: "all missing", + args: { + objectArg: {}, + nullOnlyArrayArg: [], + undefinedOnlyArrayArg: [], + nullOrUndefinedArrayArg: [], + }, + expected: [ + null, + undefined, + undefined, + { nullOnlyProp: null, undefinedOnlyProp: undefined, nullOrUndefinedProp: undefined }, + [], + [], + [], + ] + }, + { + name: "all valued", + args: { + nullOnlyArg: "a", + undefinedOnlyArg: "b", + nullOrUndefinedArg: "c", + objectArg: { + nullOnlyProp: "d", + undefinedOnlyProp: "e", + nullOrUndefinedProp: "f", + }, + nullOnlyArrayArg: ["g", "h"], + undefinedOnlyArrayArg: ["i", "j"], + nullOrUndefinedArrayArg: ["k", "l"], + }, + expected: [ + "a", + "b", + "c", + { nullOnlyProp: "d", undefinedOnlyProp: "e", nullOrUndefinedProp: "f" }, + ["g", "h"], + ["i", "j"], + ["k", "l"], + ] + }, + ]; + + for (const testCase of testCases) { + it(testCase.name, function() { + const prepared_args = prepareArguments(testCase.args, functionDefinition, objectTypes); + assert.deepStrictEqual(prepared_args, testCase.expected); + }); + } + }); +}); diff --git a/ndc-lambda-sdk/test/inference/basic-inference/basic-inference.test.ts b/ndc-lambda-sdk/test/inference/basic-inference/basic-inference.test.ts index 97b9b26..1a7dbb9 100644 --- a/ndc-lambda-sdk/test/inference/basic-inference/basic-inference.test.ts +++ b/ndc-lambda-sdk/test/inference/basic-inference/basic-inference.test.ts @@ -7,7 +7,7 @@ describe("basic inference", function() { it("simple types", function() { const schema = deriveSchema(require.resolve("./simple-types.ts")); - assert.deepEqual(schema, { + assert.deepStrictEqual(schema, { compilerDiagnostics: [], functionIssues: {}, functionsSchema: { @@ -28,7 +28,7 @@ describe("basic inference", function() { } }, "add": { - ndcKind: FunctionNdcKind.Procedure, + ndcKind: FunctionNdcKind.Function, description: null, arguments: [ { @@ -61,10 +61,10 @@ describe("basic inference", function() { }) }); - it("complex return type", function() { - const schema = deriveSchema(require.resolve("./complex-return-type.ts")); + it("object return type", function() { + const schema = deriveSchema(require.resolve("./object-return-type.ts")); - assert.deepEqual(schema, { + assert.deepStrictEqual(schema, { compilerDiagnostics: [], functionIssues: {}, functionsSchema: { @@ -149,4 +149,513 @@ describe("basic inference", function() { } }) }) + + it("complex types", function() { + const schema = deriveSchema(require.resolve("./complex-types.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: {}, + functionsSchema: { + functions: { + "bar": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [ + { + argumentName: "string", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + }, + { + argumentName: "aliasedString", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + }, + { + argumentName: "genericScalar", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + }, + { + argumentName: "array", + description: null, + type: { + type: "array", + elementType: { + type: "named", + kind: "scalar", + name: "String" + } + } + }, + { + argumentName: "anonObj", + description: null, + type: { + type: "named", + kind: "object", + name: "bar_arguments_anonObj" + } + }, + { + argumentName: "aliasedObj", + description: null, + type: { + type: "named", + kind: "object", + name: "Bar" + } + }, + { + argumentName: "genericAliasedObj", + description: null, + type: { + type: "named", + kind: "object", + name: "GenericBar" + } + }, + { + argumentName: "genericAliasedObjWithComplexTypeParam", + description: null, + type: { + kind: "object", + name: "GenericBar", + type: "named", + } + }, + { + argumentName: "interfce", + description: null, + type: { + type: "named", + kind: "object", + name: "IThing" + } + }, + { + argumentName: "genericInterface", + description: null, + type: { + type: "named", + kind: "object", + name: "IGenericThing" + } + }, + { + argumentName: "aliasedIntersectionObj", + description: null, + type: { + type: "named", + kind: "object", + name: "IntersectionObject" + } + }, + { + argumentName: "anonIntersectionObj", + description: null, + type: { + type: "named", + kind: "object", + name: "bar_arguments_anonIntersectionObj" + } + }, + { + argumentName: "genericIntersectionObj", + description: null, + type: { + type: "named", + kind: "object", + name: "GenericIntersectionObject" + } + }, + ], + resultType: { + name: "String", + kind: "scalar", + type: "named", + } + } + }, + objectTypes: { + "GenericBar": { + properties: [ + { + propertyName: "data", + type: { + kind: "object", + name: "Bar", + type: "named", + }, + }, + ] + }, + "GenericBar": { + properties: [ + { + propertyName: "data", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + "GenericIntersectionObject": { + properties: [ + { + propertyName: "data", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "test", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + Bar: { + properties: [ + { + propertyName: "test", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + IGenericThing: { + properties: [ + { + propertyName: "data", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + IThing: { + properties: [ + { + propertyName: "prop", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + IntersectionObject: { + properties: [ + { + propertyName: "wow", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "test", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + bar_arguments_anonIntersectionObj: { + properties: [ + { + propertyName: "num", + type: { + name: "Float", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "test", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + bar_arguments_anonObj: { + properties: [ + { + propertyName: "a", + type: { + name: "Float", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "b", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + ], + }, + }, + scalarTypes: { + Float: {}, + String: {}, + } + } + }) + }); + + it("nullable and undefined types", function() { + const schema = deriveSchema(require.resolve("./nullable-types.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: { + "test": [ + "Unable to derive an NDC type for function 'test' parameter 'unionWithNull' (type: string | number | null). Assuming that it is a scalar type." + ] + }, + functionsSchema: { + functions: { + "test": { + arguments: [ + { + argumentName: "myObject", + description: null, + type: { + kind: "object", + name: "MyObject", + type: "named", + }, + }, + { + argumentName: "nullableParam", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + argumentName: "undefinedParam", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + argumentName: "nullOrUndefinedParam", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsEither, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + argumentName: "unionWithNull", + description: null, + type: { + kind: "scalar", + name: "test_arguments_unionWithNull", + type: "named", + }, + }, + { + argumentName: "optionalParam", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + ], + description: null, + ndcKind: FunctionNdcKind.Procedure, + resultType: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + } + }, + }, + }, + objectTypes: { + "MyObject": { + properties: [ + { + propertyName: "string", + type: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + { + propertyName: "nullableString", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + propertyName: "optionalString", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + propertyName: "undefinedString", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsUndefinedOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + { + propertyName: "nullOrUndefinedString", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsEither, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + ], + }, + }, + scalarTypes: { + String: {}, + test_arguments_unionWithNull: {}, + }, + } + }) + }); + + it("recursive types", function() { + const schema = deriveSchema(require.resolve("./recursive-types.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: {}, + functionsSchema: { + functions: { + "bar": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [], + resultType: { + type: "named", + kind: "object", + name: "Foo" + } + } + }, + objectTypes: { + Foo: { + properties: [ + { + propertyName: "a", + type: { + type: "named", + kind: "scalar", + name: "Float" + } + }, + { + propertyName: "b", + type: { + type: "array", + elementType: { + type: "named", + kind: "object", + name: "Foo" + } + } + } + ] + }, + }, + scalarTypes: { + Float: {}, + }, + } + }) + }); }); diff --git a/ndc-lambda-sdk/test/inference/basic-inference/complex-types.ts b/ndc-lambda-sdk/test/inference/basic-inference/complex-types.ts new file mode 100644 index 0000000..3069c99 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/basic-inference/complex-types.ts @@ -0,0 +1,42 @@ +type Bar = { + test: string +} + +type GenericBar = { + data: T +} + +interface IThing { + prop: string +} + +interface IGenericThing { + data: T +} + +type IntersectionObject = { wow: string } & Bar + +type GenericIntersectionObject = { data: T } & Bar + +type AliasedString = string; + +type GenericScalar = GenericScalar2 +type GenericScalar2 = T + +export function bar( + string: string, + aliasedString: AliasedString, + genericScalar: GenericScalar, + array: string[], + anonObj: {a: number, b: string}, + aliasedObj: Bar, + genericAliasedObj: GenericBar, + genericAliasedObjWithComplexTypeParam: GenericBar, + interfce: IThing, + genericInterface: IGenericThing, + aliasedIntersectionObj: IntersectionObject, + anonIntersectionObj: {num:number} & Bar, + genericIntersectionObj: GenericIntersectionObject, + ): string { + return 'hello'; +} diff --git a/ndc-lambda-sdk/test/inference/basic-inference/nullable-types.ts b/ndc-lambda-sdk/test/inference/basic-inference/nullable-types.ts new file mode 100644 index 0000000..7ea8d02 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/basic-inference/nullable-types.ts @@ -0,0 +1,18 @@ +type MyObject = { + string: string, + nullableString: string | null, + optionalString?: string + undefinedString: string | undefined + nullOrUndefinedString: string | undefined | null +} + +export function test( + myObject: MyObject, + nullableParam: string | null, + undefinedParam: string | undefined, + nullOrUndefinedParam: string | undefined | null, + unionWithNull: string | number | null, + optionalParam?: string +): string | null { + return "test" +} diff --git a/ndc-lambda-sdk/test/inference/basic-inference/complex-return-type.ts b/ndc-lambda-sdk/test/inference/basic-inference/object-return-type.ts similarity index 100% rename from ndc-lambda-sdk/test/inference/basic-inference/complex-return-type.ts rename to ndc-lambda-sdk/test/inference/basic-inference/object-return-type.ts diff --git a/ndc-lambda-sdk/test/inference/basic-inference/recursive-types.ts b/ndc-lambda-sdk/test/inference/basic-inference/recursive-types.ts new file mode 100644 index 0000000..03e1d94 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/basic-inference/recursive-types.ts @@ -0,0 +1,14 @@ +type Foo = { + a: number, + b: Array +} + +export function bar(): Foo { + return { + a: 1, + b: [{ + a: 2, + b: [] + }] + } +} diff --git a/ndc-lambda-sdk/test/inference/basic-inference/simple-types.ts b/ndc-lambda-sdk/test/inference/basic-inference/simple-types.ts index ec69f1c..648cab0 100644 --- a/ndc-lambda-sdk/test/inference/basic-inference/simple-types.ts +++ b/ndc-lambda-sdk/test/inference/basic-inference/simple-types.ts @@ -1,10 +1,13 @@ -function non_exported() { +function nonExported() { } export function hello(): string { return 'hello world'; } +/** + * @pure + */ export function add(a: number, b: number): number { return a + b; } diff --git a/ndc-lambda-sdk/test/inference/external-dependencies/external-dependencies.test.ts b/ndc-lambda-sdk/test/inference/external-dependencies/external-dependencies.test.ts new file mode 100644 index 0000000..e76744f --- /dev/null +++ b/ndc-lambda-sdk/test/inference/external-dependencies/external-dependencies.test.ts @@ -0,0 +1,142 @@ +import { describe, it } from "mocha"; +import { assert } from "chai"; +import { deriveSchema } from "../../../src/inference"; +import { FunctionNdcKind, NullOrUndefinability } from "../../../src/schema"; + +describe("external dependencies", function() { + it("use npm package", function() { + const schema = deriveSchema(require.resolve("./use-npm-package.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: {}, + functionsSchema: { + scalarTypes: { + String: {}, + }, + objectTypes: {}, + functions: { + "useImportedPackage": { + ndcKind: FunctionNdcKind.Function, + description: null, + arguments: [ + { + argumentName: "s", + description: null, + type: { + name: "String", + kind: "scalar", + type: "named", + } + }, + ], + resultType: { + name: "String", + kind: "scalar", + type: "named", + } + } + } + } + }) + }); + + it("use postgres package", function() { + const schema = deriveSchema(require.resolve("./use-postgres-package.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: { + "delete_todos": [ + "Unable to derive an NDC type for function 'delete_todos' return value (type: string | { error: string; }). Assuming that it is a scalar type." + ], + "insert_todos": [ + "Unable to derive an NDC type for function 'insert_todos' return value (type: {} | { id: string; user_id: string; todo: string; created_at: string; } | { message: string; } | { error: string; }). Assuming that it is a scalar type." + ], + "insert_user": [ + "Unable to derive an NDC type for function 'insert_user' return value (type: {} | { id: string; name: string; created_at: string; } | { message: string; } | { error: string; }). Assuming that it is a scalar type." + ], + }, + functionsSchema: { + functions: { + "insert_user": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [ + { + argumentName: "user_name", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + } + ], + resultType: { + type: "named", + kind: "scalar", + name: "insert_user_output" + } + }, + "insert_todos": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [ + { + argumentName: "user_id", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + }, + { + argumentName: "todo", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + }, + ], + resultType: { + type: "named", + kind: "scalar", + name: "insert_todos_output" + } + }, + "delete_todos": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [ + { + argumentName: "todo_id", + description: null, + type: { + type: "named", + kind: "scalar", + name: "String" + } + } + ], + resultType: { + type: "named", + kind: "scalar", + name: "delete_todos_output" + } + }, + }, + scalarTypes: { + String: {}, + insert_todos_output: {}, + insert_user_output: {}, + delete_todos_output: {}, + }, + objectTypes: {}, + } + }) + }); +}); diff --git a/ndc-lambda-sdk/test/inference/external-dependencies/use-npm-package.ts b/ndc-lambda-sdk/test/inference/external-dependencies/use-npm-package.ts new file mode 100644 index 0000000..885a638 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/external-dependencies/use-npm-package.ts @@ -0,0 +1,9 @@ + +import { emojify } from "node-emoji"; + +/** + * @pure + */ +export function useImportedPackage(s: string): string { + return emojify(`${s} :t-rex: :heart: NPM`); +} diff --git a/ndc-lambda-sdk/test/inference/external-dependencies/use-postgres-package.ts b/ndc-lambda-sdk/test/inference/external-dependencies/use-postgres-package.ts new file mode 100644 index 0000000..3bc92e0 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/external-dependencies/use-postgres-package.ts @@ -0,0 +1,112 @@ +import { Client } from "node-postgres"; + +const dbConfig = { + user: "aaysha", + hostname: "asdfasdfasd.us-west-2.aws.neon.tech", + port: 5432, + password: "asdfasdasdf", + database: "asdfasdfasdf", + ssl: true, + sslmode: "require", +}; + +export async function insert_user( + user_name: string, +): Promise< + { id: string; name: string; created_at: string } | { message: string } | { + error: string; + } | {} +> { + const client = new Client(dbConfig); + + try { + await client.connect(); + + const result = await client.query({ + text: `INSERT INTO users(name) VALUES ('${user_name}') RETURNING *`, + }); + + if (result && result.rows.length > 0 && result.rows[0]) { + return result.rows[0]; + } else { + return { message: "Insert Failed" }; + } + } catch (error) { + console.error("Error:", error); + if (error != null && typeof error === "object" && "message" in error) { + return { error: "Error: " + error.message }; + } else { + return { error: "Unknown error" }; + } + } finally { + await client.end(); + } +} + +export async function insert_todos( + user_id: string, + todo: string + ): Promise< + { id: string; user_id: string; todo: string; created_at: string } | { message: string } | { + error: string; + } | {} + > { + const client = new Client(dbConfig); + + try { + await client.connect(); + + // Check if the user exists in the users table + const userExistsQuery = await client.query({ + text: `SELECT id FROM users where id =${user_id}` + }) + + if (userExistsQuery.rows.length === 0) { + return { message: "User not found. Insert Failed" }; + } + const result = await client.query({ + text: `INSERT INTO todos(user_id,todo) VALUES ('${user_id}','${todo}') RETURNING *`, + }); + + if (result && result.rows.length > 0 && result.rows[0]) { + return result.rows[0]; + } else { + return { message: "Insert Failed" }; + } + } catch (error) { + console.error("Error:", error); + if (error != null && typeof error === "object" && "message" in error) { + return { error: "Error: " + error.message }; + } else { + return { error: "Unknown error" }; + } + } finally { + await client.end(); + } + } + + + export async function delete_todos( + todo_id: string + ){ + const client = new Client(dbConfig); + try { + await client.connect(); + + const result = await client.query({ text: `DELETE FROM todos WHERE id =${todo_id}`}) + if (result.rowCount === 1) { + return `Deleted todo with id= ${todo_id} successfully` + } else { + return "Deletion unsuccessful" + } + } catch (error) { + if (error != null && typeof error === "object" && "message" in error) { + return { error: "Error: " + error.message }; + } else { + return { error: "Unknown error" }; + } + } finally { + client.end(); + } + + } diff --git a/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.dep.ts b/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.dep.ts new file mode 100644 index 0000000..83e2dc5 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.dep.ts @@ -0,0 +1,4 @@ +export type Foo = { + a: string, + b: number +} diff --git a/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.ts b/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.ts new file mode 100644 index 0000000..e988c1d --- /dev/null +++ b/ndc-lambda-sdk/test/inference/naming-conflicts/conflict-from-import.ts @@ -0,0 +1,16 @@ +import * as dep from './conflict-from-import.dep'; + +type Foo = { + x: boolean, + y: dep.Foo +} + +export function foo(): Foo { + return { + x: true, + y: { + a: 'hello', + b: 33 + } + } +} diff --git a/ndc-lambda-sdk/test/inference/naming-conflicts/naming-conflicts.test.ts b/ndc-lambda-sdk/test/inference/naming-conflicts/naming-conflicts.test.ts new file mode 100644 index 0000000..665dabf --- /dev/null +++ b/ndc-lambda-sdk/test/inference/naming-conflicts/naming-conflicts.test.ts @@ -0,0 +1,76 @@ +import { describe, it } from "mocha"; +import { assert } from "chai"; +import { deriveSchema } from "../../../src/inference"; +import { FunctionNdcKind } from "../../../src/schema"; + +describe("naming conflicts", function() { + it("conflict from import", function() { + const schema = deriveSchema(require.resolve("./conflict-from-import.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: {}, + functionsSchema: { + functions: { + "foo": { + ndcKind: FunctionNdcKind.Procedure, + description: null, + arguments: [], + resultType: { + name: "Foo", + kind: "object", + type: "named", + }, + } + }, + objectTypes: { + Foo: { + properties: [ + { + propertyName: "x", + type: { + name: "Boolean", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "y", + type: { + name: "conflict_from_import_dep_Foo", + kind: "object", + type: "named", + }, + }, + ] + }, + conflict_from_import_dep_Foo: { + properties: [ + { + propertyName: "a", + type: { + name: "String", + kind: "scalar", + type: "named", + }, + }, + { + propertyName: "b", + type: { + name: "Float", + kind: "scalar", + type: "named", + }, + }, + ] + }, + }, + scalarTypes: { + Boolean: {}, + Float: {}, + String: {}, + } + } + }) + }); +}); diff --git a/ndc-lambda-sdk/test/inference/unsupported-types/classes.ts b/ndc-lambda-sdk/test/inference/unsupported-types/classes.ts new file mode 100644 index 0000000..d2102bb --- /dev/null +++ b/ndc-lambda-sdk/test/inference/unsupported-types/classes.ts @@ -0,0 +1,8 @@ +class MyClass { +} + +export function bar( + clazz: MyClass + ): string { + return 'hello'; +} diff --git a/ndc-lambda-sdk/test/inference/unsupported-types/promises.ts b/ndc-lambda-sdk/test/inference/unsupported-types/promises.ts new file mode 100644 index 0000000..c77094a --- /dev/null +++ b/ndc-lambda-sdk/test/inference/unsupported-types/promises.ts @@ -0,0 +1,15 @@ +export function promiseParam(input: Promise): string { + return "" +} + +type Bar = { + str: Promise +} + +export function nestedPromiseInParam(x: Bar): string { + return ""; +} + +export function nestedPromiseInRetval(): Bar { + return { str: Promise.resolve("") }; +} diff --git a/ndc-lambda-sdk/test/inference/unsupported-types/unsupported-types.test.ts b/ndc-lambda-sdk/test/inference/unsupported-types/unsupported-types.test.ts new file mode 100644 index 0000000..cc26ed7 --- /dev/null +++ b/ndc-lambda-sdk/test/inference/unsupported-types/unsupported-types.test.ts @@ -0,0 +1,69 @@ +import { describe, it } from "mocha"; +import { assert } from "chai"; +import { deriveSchema } from "../../../src/inference"; + +describe("unsupported types", function() { + it("classes", function() { + const schema = deriveSchema(require.resolve("./classes.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: { + "bar": [ + "Class types are not supported, but one was encountered in function 'bar' parameter 'clazz'" + ] + }, + functionsSchema: { + scalarTypes: { + String: {}, + }, + objectTypes: {}, + functions: {} + } + }) + }); + + it("void", function() { + const schema = deriveSchema(require.resolve("./void.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: { + "voidFunction": [ + "The void type is not supported, but one was encountered in function 'voidFunction' return value" + ] + }, + functionsSchema: { + scalarTypes: {}, + objectTypes: {}, + functions: {} + } + }) + }); + + it("promises", function() { + const schema = deriveSchema(require.resolve("./promises.ts")); + + assert.deepStrictEqual(schema, { + compilerDiagnostics: [], + functionIssues: { + "promiseParam": [ + "Promise types are not supported, but one was encountered in function 'promiseParam' parameter 'input'." + ], + "nestedPromiseInParam": [ + "Promise types are not supported, but one was encountered in function 'nestedPromiseInParam' parameter 'x', type 'Bar' property 'str'." + ], + "nestedPromiseInRetval": [ + "Promise types are not supported, but one was encountered in function 'nestedPromiseInRetval' return value, type 'Bar' property 'str'." + ] + }, + functionsSchema: { + scalarTypes: { + String: {} + }, + objectTypes: {}, + functions: {} + } + }) + }); +}); diff --git a/ndc-lambda-sdk/test/inference/unsupported-types/void.ts b/ndc-lambda-sdk/test/inference/unsupported-types/void.ts new file mode 100644 index 0000000..042c48b --- /dev/null +++ b/ndc-lambda-sdk/test/inference/unsupported-types/void.ts @@ -0,0 +1,2 @@ +export function voidFunction(): void { +} diff --git a/ndc-lambda-sdk/test/schema/ndc-schema.test.ts b/ndc-lambda-sdk/test/schema/ndc-schema.test.ts new file mode 100644 index 0000000..9bbf5b3 --- /dev/null +++ b/ndc-lambda-sdk/test/schema/ndc-schema.test.ts @@ -0,0 +1,173 @@ +import { describe } from "mocha"; +import { FunctionNdcKind, FunctionsSchema, NullOrUndefinability, getNdcSchema } from "../../src/schema"; +import { assert } from "chai"; + +describe("ndc schema", function() { + it ("ndc schema generation", function () { + const functionsSchema: FunctionsSchema = { + functions: { + "test_proc": { + arguments: [ + { + argumentName: "nullableParam", + description: null, + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + ], + description: null, + ndcKind: FunctionNdcKind.Procedure, + resultType: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + } + }, + }, + "test_func": { + arguments: [ + { + argumentName: "myObject", + description: null, + type: { + kind: "object", + name: "MyObject", + type: "named", + }, + }, + ], + description: null, + ndcKind: FunctionNdcKind.Function, + resultType: { + type: "array", + elementType: { + kind: "scalar", + name: "String", + type: "named", + } + }, + }, + }, + objectTypes: { + "MyObject": { + properties: [ + { + propertyName: "string", + type: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + { + propertyName: "nullableString", + type: { + type: "nullable", + nullOrUndefinability: NullOrUndefinability.AcceptsNullOnly, + underlyingType: { + kind: "scalar", + name: "String", + type: "named", + }, + }, + }, + ], + }, + }, + scalarTypes: { + String: {}, + test_arguments_unionWithNull: {}, + }, + }; + + const schemaResponse = getNdcSchema(functionsSchema) + + assert.deepStrictEqual(schemaResponse, { + collections: [], + functions: [ + { + name: "test_func", + arguments: { + "myObject": { + type: { + name: "MyObject", + type: "named", + }, + }, + }, + result_type: { + type: "array", + element_type: { + name: "String", + type: "named", + } + }, + }, + ], + procedures: [ + { + name: "test_proc", + arguments: { + "nullableParam": { + type: { + type: "nullable", + underlying_type: { + name: "String", + type: "named", + }, + }, + }, + }, + result_type: { + type: "nullable", + underlying_type: { + name: "String", + type: "named", + } + }, + } + ], + object_types: { + "MyObject": { + fields: { + "string": { + type: { + name: "String", + type: "named", + }, + }, + "nullableString": { + type: { + type: "nullable", + underlying_type: { + name: "String", + type: "named", + }, + }, + }, + }, + }, + }, + scalar_types: { + String: { + aggregate_functions: {}, + comparison_operators: {} + }, + test_arguments_unionWithNull: { + aggregate_functions: {}, + comparison_operators: {} + }, + } + }); + }); +});