diff --git a/.github/workflows/check-vertexai-responses.yml b/.github/workflows/check-vertexai-responses.yml index 61f9cd45549..418ada2fc34 100644 --- a/.github/workflows/check-vertexai-responses.yml +++ b/.github/workflows/check-vertexai-responses.yml @@ -19,36 +19,44 @@ on: pull_request jobs: check-version: runs-on: ubuntu-latest + # Allow GITHUB_TOKEN to have write permissions + permissions: + contents: write steps: - - uses: actions/checkout@v4 - - name: Clone mock responses - run: scripts/update_vertexai_responses.sh - - name: Find cloned and latest versions - run: | - CLONED=$(git describe --tags) - LATEST=$(git tag --sort=v:refname | tail -n1) - echo "cloned_tag=$CLONED" >> $GITHUB_ENV - echo "latest_tag=$LATEST" >> $GITHUB_ENV - working-directory: packages/vertexai/test-utils/vertexai-sdk-test-data - - name: Find comment from previous run if exists - uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e - id: fc - with: - issue-number: ${{github.event.number}} - body-includes: Vertex AI Mock Responses Check - - name: Comment on PR if newer version is available - if: ${{env.cloned_tag != env.latest_tag && !steps.fc.outputs.comment-id}} - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 - with: - issue-number: ${{github.event.number}} - body: > - ### Vertex AI Mock Responses Check :warning: - - A newer major version of the mock responses for Vertex AI unit tests is available. - [update_vertexai_responses.sh](https://github.com/firebase/firebase-js-sdk/blob/main/scripts/update_vertexai_responses.sh) - should be updated to clone the latest version of the responses: `${{env.latest_tag}}` - - name: Delete comment when version gets updated - if: ${{env.cloned_tag == env.latest_tag && steps.fc.outputs.comment-id}} - uses: detomarco/delete-comment@850734dd44d8b15fef55b45252613b903ceb06f0 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} + - uses: actions/checkout@v4 + - name: Clone mock responses + run: scripts/update_vertexai_responses.sh + - name: Find cloned and latest versions + run: | + CLONED=$(git describe --tags) + LATEST=$(git tag --sort=v:refname | tail -n1) + echo "cloned_tag=$CLONED" >> $GITHUB_ENV + echo "latest_tag=$LATEST" >> $GITHUB_ENV + working-directory: packages/vertexai/test-utils/vertexai-sdk-test-data + - name: Find comment from previous run if exists + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{github.event.number}} + body-includes: Vertex AI Mock Responses Check + - name: Comment on PR if newer version is available + if: ${{env.cloned_tag != env.latest_tag && !steps.fc.outputs.comment-id}} + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{github.event.number}} + body: > + ### Vertex AI Mock Responses Check :warning: + + A newer major version of the mock responses for Vertex AI unit tests is available. + [update_vertexai_responses.sh](https://github.com/firebase/firebase-js-sdk/blob/main/scripts/update_vertexai_responses.sh) + should be updated to clone the latest version of the responses: `${{env.latest_tag}}` + - name: Delete comment when version gets updated + if: ${{env.cloned_tag == env.latest_tag && steps.fc.outputs.comment-id}} + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: ${{ steps.fc.outputs.comment-id }}, + }) diff --git a/.github/workflows/test-changed-firestore.yml b/.github/workflows/test-changed-firestore.yml index e5a45745f7b..0820d40835d 100644 --- a/.github/workflows/test-changed-firestore.yml +++ b/.github/workflows/test-changed-firestore.yml @@ -232,7 +232,7 @@ jobs: with: node-version: 22.10.0 - name: Download build archive - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build.tar.gz - name: Unzip build artifact @@ -259,7 +259,7 @@ jobs: if: ${{ needs.build.outputs.changed == 'true'}} steps: - name: Download build archive - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build.tar.gz - name: Unzip build artifact diff --git a/.github/workflows/test-firebase-integration.yml b/.github/workflows/test-firebase-integration.yml index 26a0cb2ead0..d77a49ecf3b 100644 --- a/.github/workflows/test-firebase-integration.yml +++ b/.github/workflows/test-firebase-integration.yml @@ -46,4 +46,4 @@ jobs: - name: build run: yarn build:changed firebase-integration - name: Run tests on changed packages - run: yarn test:changed firebase-integration \ No newline at end of file + run: yarn test:changed firebase-integration diff --git a/config/karma.base.js b/config/karma.base.js index 49824296740..fe53d3ac744 100644 --- a/config/karma.base.js +++ b/config/karma.base.js @@ -54,8 +54,8 @@ const config = { // Doing 65 seconds to allow for the 20 second firestore tests browserNoActivityTimeout: 65000, - // preprocess matching files before serving them to the browser - // available preprocessors: + // Preprocess matching files before serving them to the browser. + // Available preprocessors: // https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { 'test/**/*.ts': ['webpack', 'sourcemap'], diff --git a/packages/data-connect/README.md b/packages/data-connect/README.md new file mode 100644 index 00000000000..40d6f526624 --- /dev/null +++ b/packages/data-connect/README.md @@ -0,0 +1,5 @@ +# Firebase Data Connect + +## Local Development + +Check `test/dataconnect.yaml` to ensure that the correct values are filled in. diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index d7a8f31016e..ed3ec31cdd2 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -31,10 +31,10 @@ "prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'", "build:deps": "lerna run --scope @firebase/'{app,data-connect}' --include-dependencies build", "dev": "rollup -c -w", - "test": "run-p --npm-path npm test:emulator", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", - "test:all": "run-p --npm-path npm lint test:unit", - "test:browser": "karma start --single-run", + "test": "run-p --npm-path npm lint test:emulator", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:all": "run-p --npm-path npm lint test:browser test:node", + "test:browser": "karma start", "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:unit": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/unit/**/*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:emulator": "ts-node --compiler-options='{\"module\":\"commonjs\"}' ../../scripts/emulator-testing/dataconnect-test-runner.ts", diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index 00382686dab..a1cd0726160 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -124,7 +124,7 @@ export function queryRef( dataConnect: dcInstance, refType: QUERY_STR, name: queryName, - variables: variables + variables }; } /** diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 0a49fc9e269..7c8500b733d 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -166,6 +166,7 @@ export class RESTTransport implements DataConnectTransport { body: U ) => { const abortController = new AbortController(); + // TODO(mtewani): Update to proper value const withAuth = this.withRetry(() => dcFetch( diff --git a/packages/data-connect/test/.firebaserc b/packages/data-connect/test/.firebaserc new file mode 100644 index 00000000000..5ea9e18457f --- /dev/null +++ b/packages/data-connect/test/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "jscore-sandbox-141b5" + } +} diff --git a/packages/data-connect/test/.gitignore b/packages/data-connect/test/.gitignore new file mode 100644 index 00000000000..b17f6310755 --- /dev/null +++ b/packages/data-connect/test/.gitignore @@ -0,0 +1,69 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# dataconnect generated files +.dataconnect diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql deleted file mode 100755 index 8c472f99a6e..00000000000 --- a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql +++ /dev/null @@ -1,49 +0,0 @@ -scalar Movie_Key -input Movie_Data { - id: String - id_expr: String_Expr - description: String - description_expr: String_Expr - genre: String - genre_expr: String_Expr - name: String - name_expr: String_Expr - test: String - test_expr: String_Expr -} -input Movie_Filter { - _and: [Movie_Filter!] - _not: Movie_Filter - _or: [Movie_Filter!] - id: String_Filter - description: String_Filter - genre: String_Filter - name: String_Filter - test: String_Filter -} -input Movie_ListFilter { - count: Int_Filter - exist: Movie_Filter -} -input Movie_ListUpdate { - append: [Movie_Data!] - delete: Int - i: Int - prepend: [Movie_Data!] - set: [Movie_Data!] - update: [Movie_Update!] -} -input Movie_Order { - id: OrderDirection - description: OrderDirection - genre: OrderDirection - name: OrderDirection - test: OrderDirection -} -input Movie_Update { - id: [String_Update!] - description: [String_Update!] - genre: [String_Update!] - name: [String_Update!] - test: [String_Update!] -} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql deleted file mode 100755 index b6896486e4a..00000000000 --- a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql +++ /dev/null @@ -1,8 +0,0 @@ -extend type Mutation { - movie_insert(data: Movie_Data!): Movie_Key! - movie_upsert(data: Movie_Data!, update: Movie_Update): Movie_Key! - movie_update(id: String, id_expr: String_Expr, key: Movie_Key, data: Movie_Data, update: Movie_Update): Movie_Key - movie_updateMany(where: Movie_Filter, all: Boolean = false, data: Movie_Data, update: Movie_Update): Int! - movie_delete(id: String, id_expr: String_Expr, key: Movie_Key): Movie_Key - movie_deleteMany(where: Movie_Filter, all: Boolean = false): Int! -} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql deleted file mode 100755 index 53ee30ce8ad..00000000000 --- a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql +++ /dev/null @@ -1,4 +0,0 @@ -extend type Query { - movie(id: String, id_expr: String_Expr, key: Movie_Key): Movie - movies(where: Movie_Filter, orderBy: [Movie_Order!], limit: Int = 100): [Movie!] -} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql deleted file mode 100755 index 4007a693025..00000000000 --- a/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql +++ /dev/null @@ -1,953 +0,0 @@ -"Conditions on a string value" -input String_Filter { - isNull: Boolean - eq: String - eq_expr: String_Expr - ne: String - ne_expr: String_Expr - in: [String!] - nin: [String!] - gt: String - ge: String - lt: String - le: String - contains: String - startsWith: String - endsWith: String - pattern: String_Pattern -} - -""" -The pattern match condition on a string. Specify either like or regex. -https://www.postgresql.org/docs/current/functions-matching.html -""" -input String_Pattern { - "the LIKE expression to use" - like: String - "the POSIX regular expression" - regex: String - "when true, it's case-insensitive. In Postgres: ILIKE, ~*" - ignoreCase: Boolean - "when true, invert the condition. In Postgres: NOT LIKE, !~" - invert: Boolean -} - -"Conditions on a string list" -input String_ListFilter { - includes: String - excludes: String - includesAll: [String!] - excludesAll: [String!] -} - -"Conditions on a UUID value" -input UUID_Filter { - isNull: Boolean - eq: UUID - ne: UUID - in: [UUID!] - nin: [UUID!] -} - -"Conditions on a UUID list" -input UUID_ListFilter { - includes: UUID - excludes: UUID - includesAll: [UUID!] - excludesAll: [UUID!] -} - -"Conditions on an Int value" -input Int_Filter { - isNull: Boolean - eq: Int - ne: Int - in: [Int!] - nin: [Int!] - gt: Int - ge: Int - lt: Int - le: Int -} - -"Conditions on an Int list" -input Int_ListFilter { - includes: Int - excludes: Int - includesAll: [Int!] - excludesAll: [Int!] -} - -"Conditions on an Int64 value" -input Int64_Filter { - isNull: Boolean - eq: Int64 - ne: Int64 - in: [Int64!] - nin: [Int64!] - gt: Int64 - ge: Int64 - lt: Int64 - le: Int64 -} - -"Conditions on an Int64 list" -input Int64_ListFilter { - includes: Int64 - excludes: Int64 - includesAll: [Int64!] - excludesAll: [Int64!] -} - -"Conditions on a Float value" -input Float_Filter { - isNull: Boolean - eq: Float - ne: Float - in: [Float!] - nin: [Float!] - gt: Float - ge: Float - lt: Float - le: Float -} - -"Conditions on a Float list" -input Float_ListFilter { - includes: Float - excludes: Float - includesAll: [Float!] - excludesAll: [Float!] -} - -"Conditions on a Boolean value" -input Boolean_Filter { - isNull: Boolean - eq: Boolean - ne: Boolean - in: [Boolean!] - nin: [Boolean!] -} - -"Conditions on a Boolean list" -input Boolean_ListFilter { - includes: Boolean - excludes: Boolean - includesAll: [Boolean!] - excludesAll: [Boolean!] -} - -"Conditions on a Date value" -input Date_Filter { - isNull: Boolean - eq: Date - ne: Date - in: [Date!] - nin: [Date!] - gt: Date - ge: Date - lt: Date - le: Date - """ - Offset the date filters by a fixed duration. - last 3 months is {ge: {today: true}, offset: {months: -3}} - """ - offset: Date_Offset -} - -"Duration to offset a date value" -input Date_Offset { - days: Int - months: Int - years: Int -} - -"Conditions on a Date list" -input Date_ListFilter { - includes: Date - excludes: Date - includesAll: [Date!] - excludesAll: [Date!] -} - -"Conditions on an Timestamp value" -input Timestamp_Filter { - isNull: Boolean - eq: Timestamp - eq_expr: Timestamp_Expr - ne: Timestamp - ne_expr: Timestamp_Expr - in: [Timestamp!] - nin: [Timestamp!] - gt: Timestamp - gt_expr: Timestamp_Expr - ge: Timestamp - ge_expr: Timestamp_Expr - lt: Timestamp - lt_expr: Timestamp_Expr - le: Timestamp - le_expr: Timestamp_Expr - - """ - Offset timestamp input by a fixed duration. - in 12h is {le: {now: true}, offset: {hours: 12}} - """ - offset: Timestamp_Offset @deprecated -} - -"Duration to offset a timestamp value" -input Timestamp_Offset @fdc_deprecated { - milliseconds: Int - seconds: Int - minutes: Int - hours: Int - days: Int - months: Int - years: Int -} - -"Conditions on a Timestamp list" -input Timestamp_ListFilter { - includes: Timestamp - includes_expr: Timestamp_Expr - excludes: Timestamp - excludes_expr: Timestamp_Expr - includesAll: [Timestamp!] - excludesAll: [Timestamp!] -} - -"Conditions on an Any value" -input Any_Filter { - isNull: Boolean - eq: Any - ne: Any - in: [Any!] - nin: [Any!] -} - -"Conditions on a Any list" -input Any_ListFilter { - includes: Any - excludes: Any - includesAll: [Any!] - excludesAll: [Any!] -} - -"Conditions on an AuthUID value" -input AuthUID_Filter @fdc_deprecated { - eq: AuthUID - ne: AuthUID - in: [AuthUID!] - nin: [AuthUID!] - isNull: Boolean -} - -input AuthUID_ListFilter @fdc_deprecated { - "When true, will match if the list includes the id of the current user." - includes: AuthUID - excludes: AuthUID - includesAll: [AuthUID!] - excludesAll: [AuthUID!] -} - -"Conditions on an Vector value" -input Vector_Filter { - eq: Vector - ne: Vector - in: [Vector!] - nin: [Vector!] - isNull: Boolean -} - -input Vector_ListFilter { - "When true, will match if the list includes the supplied vector." - includes: Vector - excludes: Vector - includesAll: [Vector!] - excludesAll: [Vector!] -} -type Query { - _service: _Service! -} - -type Mutation { - # This is just a dummy field so that Mutation is always non-empty. - _firebase: Void @fdc_deprecated(reason: "dummy field -- does nothing useful") -} - -type _Service { - sdl: String! -} - -"(Internal) Added to things that may be removed from FDC and will soon be no longer usable in schema or operations." -directive @fdc_deprecated(reason: String = "No longer supported") on - | SCHEMA - | SCALAR - | OBJECT - | FIELD_DEFINITION - | ARGUMENT_DEFINITION - | INTERFACE - | UNION - | ENUM - | ENUM_VALUE - | INPUT_OBJECT - | INPUT_FIELD_DEFINITION - -"(Internal) Added to scalars representing quoted CEL expressions." -directive @fdc_celExpression( - "The expected CEL type that the expression should evaluate to." - returnType: String -) on SCALAR - -"(Internal) Added to scalars representing quoted SQL expressions." -directive @fdc_sqlExpression( - "The expected SQL type that the expression should evaluate to." - dataType: String -) on SCALAR - -"(Internal) Added to types that may not be used as variables." -directive @fdc_forbiddenAsVariableType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT - -"(Internal) Added to types that may not be used as fields in schema." -directive @fdc_forbiddenAsFieldType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT - -"Provides a frequently used example for this type / field / argument." -directive @fdc_example( - "A GraphQL literal value (verbatim) whose type matches the target." - value: Any - "A human-readable text description of what `value` means in this context." - description: String -) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | INPUT_OBJECT | INPUT_FIELD_DEFINITION - -"(Internal) Marks this field / argument as conflicting with others in the same group." -directive @fdc_oneOf( - "The group name where fields / arguments conflict with each other." - group: String! = "" - "If true, exactly one field / argument in the group must be specified." - required: Boolean! = false -) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION -"AccessLevel specifies coarse access policies for common situations." -enum AccessLevel { - """ - This operation can be executed by anyone with or without authentication. - Equivalent to @auth(expr: "true") - """ - PUBLIC - - """ - This operation can only be executed with a valid Firebase Auth ID token. - Note: it allows anonymous auth and unverified accounts, so may be subjected to abuses. - It’s equivalent to @auth(expr: "auth.uid != nil") - """ - USER_ANON - - """ - This operation can only be executed by a non-anonymous Firebase Auth account. - It’s equivalent to @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")" - """ - USER - - """ - This operation can only be executed by a verified Firebase Auth account. - It’s equivalent to @auth(expr: "auth.uid != nil && auth.token.email_verified")" - """ - USER_EMAIL_VERIFIED - - """ - This operation can not be executed with no IAM credentials. - It’s equivalent to @auth(expr: "false") - """ - NO_ACCESS -} - -""" -Defines the auth policy for a query or mutation. This directive must be added to -any operation you wish to be accessible from a client application. If left -unspecified, defaults to `@auth(level: NO_ACCESS)`. -""" -directive @auth( - "The minimal level of access required to perform this operation." - level: AccessLevel @fdc_oneOf(required: true) - """ - A CEL expression that allows access to this operation if the expression - evaluates to `true`. - """ - expr: Boolean_Expr @fdc_oneOf(required: true) -) on QUERY | MUTATION -""" -Mark this field as a customized resolver. -It may takes customized input arguments and return customized types. - -TODO(b/315857408): Funnel this through API review. - -See: -- go/firemat:custom-resolvers -- go/custom-resolvers-hackweek -""" -directive @resolver on FIELD_DEFINITION -scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") -scalar Int64 -scalar Date -scalar Timestamp @specifiedBy(url: "https://scalars.graphql.org/andimarek/date-time") -scalar Any -scalar Void -""" -AuthUID is a string representing a Firebase Auth uid. When passing a literal -value for an AuthUID in a query, you may instead pass `{current: true}` and the -currently signed in user's uid will be injected instead. For example: - -```gql -type Order { - customerId: AuthUID! - # ... -} - -query myOrders { - orders: (where: { - customerId: {eq: {current: true}} - }) { customerId } -} -``` -""" -scalar AuthUID @fdc_deprecated -scalar Vector -"Define the intervals used in timestamps and dates (subset)" -enum TimestampInterval @fdc_deprecated { - second - minute - hour - day - week - month - year -} - -input Timestamp_Sentinel @fdc_deprecated { - "Return the current time." - now: Boolean, - "Defines a timestamp relative to the current time. Offset values can be positive or negative." - fromNow: Timestamp_Offset - "Truncate the current/offset time to the specified interval." - truncateTo: TimestampInterval -} - -""" -A Common Expression Language (CEL) expression that returns a boolean at runtime. - -The expression can reference the `auth` variable, which is null if Firebase Auth -is not used. Otherwise, it contains the following fields: - - - `auth.uid`: The current user ID. - - `auth.token`: A map of all token fields (i.e. "claims"). -""" -scalar Boolean_Expr - @specifiedBy(url: "https://github.com/google/cel-spec") - @fdc_celExpression(returnType: "bool") - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType - @fdc_example(value: "auth != null", description: "Allow only if a Firebase Auth user is present.") - -""" -A Common Expression Language (CEL) expression that returns a string at runtime. - -Limitation: Right now, only a few expressions are supported. Those are listed -using the @fdc_example directive on this scalar. -""" -scalar String_Expr - @specifiedBy(url: "https://github.com/google/cel-spec") - @fdc_celExpression(returnType: "string") - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType - @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") - @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) string, formatted as 32 lower-case hex digits without delimiters.") - -""" -A Common Expression Language (CEL) expression that returns a Timestamp at runtime. - -Limitation: Right now, only a few expressions are supported. Those are listed -using the @fdc_example directive on this scalar. -""" -scalar Timestamp_Expr - @specifiedBy(url: "https://github.com/google/cel-spec") - @fdc_celExpression(returnType: "google.protobuf.Timestamp") - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType - @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") - -""" -A Common Expression Language (CEL) expression that returns a UUID string at runtime. - -Limitation: Right now, only a few expressions are supported. Those are listed -using the @fdc_example directive on this scalar. -""" -scalar UUID_Expr - @specifiedBy(url: "https://github.com/google/cel-spec") - @fdc_celExpression(returnType: "string") - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType - @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) every time.") - -""" -A Common Expression Language (CEL) expression whose return type is unspecified. - -Limitation: Only a limited set of expressions are supported for now for each -type. For type XXX, see the @fdc_example directives on XXX_Expr for a full list. -""" -scalar Any_Expr - @specifiedBy(url: "https://github.com/google/cel-spec") - @fdc_celExpression - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType - @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") - @fdc_example(value: "uuidV4()", description: "Generates a new random UUID version 4 (formatted as 32 lower-case hex digits without delimiters if result type is String).") - @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") - -""" -A PostgreSQL value expression whose return type is unspecified. -""" -scalar Any_SQL - @specifiedBy(url: "https://www.postgresql.org/docs/current/sql-expressions.html") - @fdc_sqlExpression - @fdc_forbiddenAsVariableType - @fdc_forbiddenAsFieldType -""" -Defines a relational database table. - -Given `type TableName @table`, - - - `TableName` is the GQL type name. - - `tableName` is the singular name. Override with `@table(singular)`. - - `tableNames` is the plural name. Override with `@table(plural)`. - - `table_name` is the SQL table id. Override with `@table(name)`. - -Only a table type can be configured further with: - - - Customized data types. See `@col`. - - Index. See `@index` - - Unique constraint. See `@unqiue` - - Relation. See `@ref` - - Embedded Json. See `@embed` - -A scalar field map to a SQL database column. -An object field (like `type TableName @table { field: AnotherType }`) are either - - - a relation reference field if `AnotherType` is annotated with `@table`. - - an embedded json field if `field` is annotated with `@embed`. - -""" -directive @table( - "Override the SQL database table name. Defaults to ." - name: String - "Override the singular name. Default is the camel case of the type name." - singular: String - "Override the plural name. Default is generated based on English patterns." - plural: String - "The primary key of the table. Defaults to a single field `id: UUID!`. Generate if missing." - key: [String!] -) on OBJECT - -""" -Defines a relational database view. - -Given `type ViewName @view`, - - `ViewName` is the GQL type name. - - `viewName` is the singular name. Override with `@view(singular)`. - - `viewNames` is the plural name. Override with `@view(plural)`. - - `view_name` is the SQL view id. Override with `@view(name)`. - When `@view(sql)` is defined, it uses the given raw SQL as the view instead. - -A view type can be used just as a table type with queries. -A view type may have a nullable `@ref` field to another table, but cannot be -referenced in a `@ref`. - -WARNING: Firebase Data Connect does not validate the SQL of the view or -evaluate whether it matches the defined fields. - -If the SQL view is invalid or undefined, related requests may fail. -If the SQL view return incompatible types. Firebase Data Connect will surface -an error in the response. -""" -directive @view( - """ - The SQL view name. If no `name` or `sql` are provided, defaults to - snake_case of the singular type name. - """ - name: String @fdc_oneOf - """ - SQL SELECT statement to use as the basis for this type. Note that all SQL - identifiers should be snake_case and all GraphQL identifiers should be - camelCase. - """ - sql: String @fdc_oneOf - "Override the singular name. Default is the camel case of the type name." - singular: String - "Override the plural name. Default is generated based on English patterns." - plural: String -) on OBJECT - -""" -Specify additional column options. - -Given `type TableName @table { fieldName: Int } ` - - - `field_name` is the SQL column name. Override with `@col(name)`. - -""" -directive @col( - "The SQL database column name. Defaults to ." - name: String - """ - Override SQL columns data type. - Each GraphQL type could map to many SQL data types. - Refer to Postgres supported data types and mappings to GQL. - """ - dataType: String - """ - Defines a fixed column size for certain scalar types. - - - For Vector, size is required. It establishes the length of the vector. - - For String, size converts `text` type to `varchar(size)`. - """ - size: Int -) on FIELD_DEFINITION - - -""" -Define an embedded JSON field represented as Postgres `jsonb` (or `json`). - -Given `type TableName @table { fieldName: EmbeddedType @embed }` -`EmbeddedType` must NOT have `@table`. - - - Store JSON object if `EmbeddedType`. Required column if `EmbeddedType!`. - - Store JSON array if `[EmbeddedType]`. Required column if `[EmbeddedType]!`. - -""" -directive @embed on FIELD_DEFINITION - -""" -Define a reference field to another table. - -Given `type TableName @table { refField: AnotherTableName }`, it defines a foreign-key constraint - - - with id `table_name_ref_field_fkey` (override with `@ref(constraintName)`) - - from `table_name.ref_field` (override with `@ref(fields)`) - - to `another_table_name.id` (override with `@ref(references)`) - -Does not support `[AnotherTableName]` because array fields cannot have foreign-key constraints. -Nullability determines whether the reference is required. - - - `refField: AnotherTableName`: optional reference, SET_NULL on delete. - - `refField: AnotherTableName!`: required reference, CASCADE on delete. - -Consider all types of SQL relations: - - - many-to-one relations involve a reference field on the many-side. - - many-to-maybe-one if `refField: AnotherTableName`. - - many-to-exactly-one if `refField: AnotherTableName!`. - - one-to-one relations involve a unique reference field on one side. - - maybe-one-to-maybe-one if `refField: AnotherTableName @unique`. - - maybe-one-to-exact-one if `refField: AnotherTableName! @unique`. - - exact-one-to-exact-one shall be represented as a single table instead. - - many-to-many relations involve a join table. - - Its primary keys must be two non-null reference fields to tables bridged together to guarantee at most one relation per pair. - -type TableNameToAnotherTableName @table(key: ["refField", "anotherRefField"]) { - refField: TableName! - anotherRefField: AnotherTableName! -} - -""" -directive @ref( - "The SQL database foreign key constraint name. Default to __fkey." - constraintName: String - """ - Foreign key fields. Default to . - """ - fields: [String!] - "The fields that the foreign key references in the other table. Default to the primary key." - references: [String!] -) on FIELD_DEFINITION - -enum IndexFieldOrder { ASC DESC } - -""" -Defines a database index to optimize query performance. - -Given `type TableName @table @index(fields: [“fieldName”, “secondFieldName”])`, -`table_name_field_name_second_field_name_aa_idx` is the SQL index id. -`table_name_field_name_second_field_name_ad_idx` if `order: [ASC DESC]`. -`table_name_field_name_second_field_name_dd_idx` if `order: [DESC DESC]`. - -Given `type TableName @table { fieldName: Int @index } ` -`table_name_field_name_idx` is the SQL index id. -`order` matters less for single field indexes because they can be scanned in both ways. - -Override with `@index(name)` in case of index name conflicts. -""" -directive @index( - "The SQL database index id. Defaults to __idx." - name: String - """ - Only allowed and required when used on OBJECT. - The fields to create an index on. - """ - fields: [String!] - """ - Only allowed when used on OBJECT. - Index order of each column. Default to all ASC. - """ - order: [IndexFieldOrder!] -) repeatable on FIELD_DEFINITION | OBJECT - -""" -Defines a unique constraint. - -Given `type TableName @table @unique(fields: [“fieldName”, “secondFieldName”])`, -`table_name_field_name_second_field_name_uidx` is the SQL unique index id. -Given `type TableName @table { fieldName: Int @unique } ` -`table_name_field_name_idx` is the SQL unique index id. - -Override with `@unique(indexName)` in case of index name conflicts. -""" -directive @unique( - "The SQL database unique index name. Defaults to __uidx." - indexName: String - """ - Only allowed and required when used on OBJECT. - The fields to create a unique constraint on. - """ - fields: [String!] -) repeatable on FIELD_DEFINITION | OBJECT - -"Define the direction of an orderby query" -enum OrderDirection { - ASC - DESC -} - -""" -Defines what siliarlity function to use for fetching vectors. -Details here: https://github.com/pgvector/pgvector?tab=readme-ov-file#vector-functions -""" -enum VectorSimilarityMethod { - L2 - COSINE - INNER_PRODUCT -} - -enum ColDefault @fdc_deprecated { - """ - Generates a random UUID (v4) as the default column value. - Compatible with String or UUID typed fields. - """ - UUID - """ - Generates an auto-incrementing sequence as the default column value. - Compatible with Int and Int64 typed fields. - """ - SEQUENCE - """ - Populates the default column value with the current date or timestamp. - Compatible with Date and Timestamp typed fields. - """ - NOW -} - -""" -Specify the default column value. - -The supported arguments vary based on the field type. -""" -directive @default( - "A constant value. Validated against the field GraphQL type at compile-time." - value: Any @fdc_oneOf(required: true) - "(Deprecated) Built-in common ways to generate initial value." - generate: ColDefault @fdc_oneOf(required: true) @deprecated - "A CEL expression, whose return value must match the field data type." - expr: Any_Expr @fdc_oneOf(required: true) - """ - A raw SQL expression, whose SQL data type must match the underlying column. - - The value is any variable-free expression (in particular, cross-references to - other columns in the current table are not allowed). Subqueries are not allowed either. - https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-DEFAULT - """ - sql: Any_SQL @fdc_oneOf(required: true) -) on FIELD_DEFINITION -"Update input of a String value" -input String_Update { - set: String @fdc_oneOf(group: "set") - set_expr: String_Expr @fdc_oneOf(group: "set") -} - -"Update input of a String list value" -input String_ListUpdate { - set: [String!] - append: [String!] - prepend: [String!] - delete: Int - i: Int - update: String -} - -"Update input of a UUID value" -input UUID_Update { - set: UUID @fdc_oneOf(group: "set") - set_expr: UUID_Expr @fdc_oneOf(group: "set") -} - -"Update input of an ID list value" -input UUID_ListUpdate { - set: [UUID!] - append: [UUID!] - prepend: [UUID!] - delete: Int - i: Int - update: UUID -} - -"Update input of an Int value" -input Int_Update { - set: Int - inc: Int - dec: Int -} - -"Update input of an Int list value" -input Int_ListUpdate { - set: [Int!] - append: [Int!] - prepend: [Int!] - delete: Int - i: Int - update: Int -} - -"Update input of an Int64 value" -input Int64_Update { - set: Int64 - inc: Int64 - dec: Int64 -} - -"Update input of an Int64 list value" -input Int64_ListUpdate { - set: [Int64!] - append: [Int64!] - prepend: [Int64!] - delete: Int - i: Int - update: Int64 -} - -"Update input of a Float value" -input Float_Update { - set: Float - inc: Float - dec: Float -} - -"Update input of a Float list value" -input Float_ListUpdate { - set: [Float!] - append: [Float!] - prepend: [Float!] - delete: Int - i: Int - update: Float -} - -"Update input of a Boolean value" -input Boolean_Update { - set: Boolean -} - -"Update input of a Boolean list value" -input Boolean_ListUpdate { - set: [Boolean!] - append: [Boolean!] - prepend: [Boolean!] - delete: Int - i: Int - update: Boolean -} - -"Update input of a Date value" -input Date_Update { - set: Date - inc: Date_Offset - dec: Date_Offset -} - -"Update input of a Date list value" -input Date_ListUpdate { - set: [Date!] - append: [Date!] - prepend: [Date!] - delete: Int - i: Int - update: Date -} - -"Update input of a Timestamp value" -input Timestamp_Update { - set: Timestamp @fdc_oneOf(group: "set") - set_expr: Timestamp_Expr @fdc_oneOf(group: "set") - inc: Timestamp_Offset - dec: Timestamp_Offset -} - -"Update input of a Timestamp list value" -input Timestamp_ListUpdate { - set: [Timestamp!] - append: [Timestamp!] - prepend: [Timestamp!] - delete: Int - i: Int - update: Timestamp -} - -"Update input of an Any value" -input Any_Update { - set: Any -} - -"Update input of an Any list value" -input Any_ListUpdate { - set: [Any!] - append: [Any!] - prepend: [Any!] - delete: Int - i: Int - update: Any -} - -"Update input of an AuthUID value" -input AuthUID_Update @fdc_deprecated { - set: AuthUID -} - -"Update input of an AuthUID list value" -input AuthUID_ListUpdate @fdc_deprecated { - set: [AuthUID] - append: [AuthUID] - prepend: [AuthUID] - delete: Int - i: Int - update: AuthUID -} - -"Update input of an Vector value" -input Vector_Update { - set: Vector -} - -"Update input of a Vector list value" -input Vector_ListUpdate { - set: [Vector] - append: [Vector] - prepend: [Vector] - delete: Int - i: Int - update: Vector -} diff --git a/packages/data-connect/test/dataconnect/connector/connector.yaml b/packages/data-connect/test/dataconnect/connector/connector.yaml index 064d9c2c184..e945b44b00c 100644 --- a/packages/data-connect/test/dataconnect/connector/connector.yaml +++ b/packages/data-connect/test/dataconnect/connector/connector.yaml @@ -1,6 +1,6 @@ -connectorId: "movies" +connectorId: "tests" authMode: "PUBLIC" generate: javascriptSdk: outputDir: "./gen/web" - package: "@movie-app-ssr/movies" + package: "@test-app/tests" diff --git a/packages/data-connect/test/dataconnect/connector/mutations.gql b/packages/data-connect/test/dataconnect/connector/mutations.gql index e69de29bb2d..6a4f3d30142 100644 --- a/packages/data-connect/test/dataconnect/connector/mutations.gql +++ b/packages/data-connect/test/dataconnect/connector/mutations.gql @@ -0,0 +1,10 @@ +mutation RemovePost($id: UUID!) @auth(level: PUBLIC) { + post_delete(id: $id) +} +mutation AddPost($id: UUID!, $description: String!, $testId: String!) @auth(level: PUBLIC) { + post_insert(data: { + id: $id, + description: $description, + testId: $testId + }) +} diff --git a/packages/data-connect/test/dataconnect/connector/queries.gql b/packages/data-connect/test/dataconnect/connector/queries.gql index e69de29bb2d..49eb7533761 100644 --- a/packages/data-connect/test/dataconnect/connector/queries.gql +++ b/packages/data-connect/test/dataconnect/connector/queries.gql @@ -0,0 +1,17 @@ +query ListPosts($testId: String!) @auth(level: PUBLIC) { + posts(where: { + testId: { + eq: $testId + } + }) { + id, + description, + } +} + +query UnauthorizedQuery { + posts { + id, + description + } +} diff --git a/packages/data-connect/test/dataconnect/dataconnect.yaml b/packages/data-connect/test/dataconnect/dataconnect.yaml index 442e98e5592..06e5380c534 100644 --- a/packages/data-connect/test/dataconnect/dataconnect.yaml +++ b/packages/data-connect/test/dataconnect/dataconnect.yaml @@ -1,11 +1,12 @@ specVersion: "v1beta" -serviceId: "dataconnect" +serviceId: "fdc-service" location: "us-west2" schema: source: "./schema" datasource: postgresql: - database: "dataconnect-test" + database: "tests" cloudSql: - instanceId: "local" + instanceId: "prod" + # schemaValidation: "COMPATIBLE" connectorDirs: ["./connector"] diff --git a/packages/data-connect/test/dataconnect/schema/schema.gql b/packages/data-connect/test/dataconnect/schema/schema.gql index 1b9ca01d832..3ccfaa951a1 100644 --- a/packages/data-connect/test/dataconnect/schema/schema.gql +++ b/packages/data-connect/test/dataconnect/schema/schema.gql @@ -14,10 +14,7 @@ # product: Product! # quantity: Int! # } -type Movie @table { - id: String! - name: String! - genre: String! +type Post @table { description: String! - test: String + testId: String! } diff --git a/packages/data-connect/test/firebase.json b/packages/data-connect/test/firebase.json new file mode 100644 index 00000000000..6c9ad90b440 --- /dev/null +++ b/packages/data-connect/test/firebase.json @@ -0,0 +1,10 @@ +{ + "emulators": { + "dataconnect": { + "dataDir": "dataconnect/.dataconnect/pgliteData" + } + }, + "dataconnect": { + "source": "dataconnect" + } +} diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index 8b630242a4e..24db1e4508f 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -29,86 +29,86 @@ import { queryRef, QueryResult, SerializedRef, - subscribe, - terminate, SOURCE_CACHE, - SOURCE_SERVER + SOURCE_SERVER, + subscribe, + terminate } from '../src'; -import { setupQueries } from './emulatorSeeder'; import { getConnectionConfig, initDatabase, PROJECT_ID } from './util'; use(chaiAsPromised); -interface Task { +interface Post { id: string; - content: string; + description: string; } -interface TaskListResponse { - posts: Task[]; +interface PostListResponse { + posts: Post[]; } const SEEDED_DATA = [ { id: crypto.randomUUID(), - content: 'task 1' + description: 'task 1' }, { id: crypto.randomUUID(), - content: 'task 2' + description: 'task 2' } ]; const REAL_DATA = SEEDED_DATA.map(obj => ({ ...obj, id: obj.id.replace(/-/g, '') })); -function seedDatabase(instance: DataConnect): Promise { - // call mutation query that adds SEEDED_DATA to database - return new Promise((resolve, reject) => { - async function run(): Promise { - let idx = 0; - while (idx < SEEDED_DATA.length) { - const data = SEEDED_DATA[idx]; - const ref = mutationRef(instance, 'seedDatabase', data); - await executeMutation(ref); - idx++; - } - } - run().then(resolve, reject); - }); -} + async function deleteDatabase(instance: DataConnect): Promise { for (let i = 0; i < SEEDED_DATA.length; i++) { const data = SEEDED_DATA[i]; - const ref = mutationRef(instance, 'removePost', { id: data.id }); + const ref = mutationRef(instance, 'RemovePost', { id: data.id }); + await executeMutation(ref); + } +} +async function seedDatabase( + instance: DataConnect, + testId: string +): Promise { + for (let i = 0; i < SEEDED_DATA.length; i++) { + const data = { ...SEEDED_DATA[i], testId }; + const ref = mutationRef(instance, 'AddPost', data); await executeMutation(ref); } } +interface PostVariables { + testId: string; +} describe('DataConnect Tests', async () => { let dc: DataConnect; + const TEST_ID = crypto.randomUUID(); beforeEach(async () => { dc = initDatabase(); - await setupQueries('queries.schema.gql', [ - { type: 'query', name: 'post' }, - { type: 'mutation', name: 'mutations' } - ]); - await seedDatabase(dc); + await seedDatabase(dc, TEST_ID); }); afterEach(async () => { await deleteDatabase(dc); await terminate(dc); }); + function getPostsRef(): QueryRef { + return queryRef(dc, 'ListPosts', { + testId: TEST_ID + }); + } it('Can get all posts', async () => { - const taskListQuery = queryRef(dc, 'listPosts'); + const taskListQuery = getPostsRef(); const taskListRes = await executeQuery(taskListQuery); expect(taskListRes.data).to.deep.eq({ posts: REAL_DATA }); }); it(`instantly executes a query if one hasn't been subscribed to`, async () => { - const taskListQuery = queryRef(dc, 'listPosts'); - const promise = new Promise>( + const taskListQuery = getPostsRef(); + const promise = new Promise>( (resolve, reject) => { const unsubscribe = subscribe(taskListQuery, { onNext: res => { @@ -129,17 +129,17 @@ describe('DataConnect Tests', async () => { expect(res.source).to.eq(SOURCE_SERVER); }); it(`returns the result source as cache when data already exists`, async () => { - const taskListQuery = queryRef(dc, 'listPosts'); + const taskListQuery = getPostsRef(); const queryResult = await executeQuery(taskListQuery); const result = await waitForFirstEvent(taskListQuery); expect(result.data).to.eq(queryResult.data); expect(result.source).to.eq(SOURCE_CACHE); }); it(`returns the proper JSON when calling .toJSON()`, async () => { - const taskListQuery = queryRef(dc, 'listPosts'); + const taskListQuery = getPostsRef(); await executeQuery(taskListQuery); const result = await waitForFirstEvent(taskListQuery); - const serializedRef: SerializedRef = { + const serializedRef: SerializedRef = { data: { posts: REAL_DATA }, @@ -150,12 +150,14 @@ describe('DataConnect Tests', async () => { projectId: PROJECT_ID }, name: taskListQuery.name, - variables: undefined + variables: { testId: TEST_ID } }, source: SOURCE_CACHE }; - expect(result.toJSON()).to.deep.eq(serializedRef); - expect(result.source).to.deep.eq(SOURCE_CACHE); + const json = result.toJSON(); + expect(json.data).to.deep.eq(serializedRef.data); + expect(json.refInfo).to.deep.eq(serializedRef.refInfo); + expect(json.source).to.deep.eq(serializedRef.source); }); it(`throws an error when the user can't connect to the server`, async () => { // You can't point an existing data connect instance to a new emulator port, so we have to create a new one @@ -165,13 +167,16 @@ describe('DataConnect Tests', async () => { service: 'wrong' }); connectDataConnectEmulator(fakeInstance, 'localhost', 3512); - const taskListQuery = queryRef(fakeInstance, 'listPosts'); + const taskListQuery = queryRef(fakeInstance, 'ListPosts'); await expect(executeQuery(taskListQuery)).to.eventually.be.rejectedWith( - 'ECONNREFUSED' + /EADDRNOTAVAIL|ECONNREFUSED|Failed to fetch/ ); }); it('throws an error with just the message when the server responds with an error', async () => { - const invalidTaskListQuery = queryRef(dc, 'listPosts2'); + const invalidTaskListQuery = queryRef( + dc, + 'UnauthorizedQuery' + ); const message = 'unauthorized: you are not authorized to perform this operation'; await expect( diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts index 314c8a068dc..76d04e3b502 100644 --- a/packages/data-connect/test/unit/dataconnect.test.ts +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -18,9 +18,10 @@ import { deleteApp, initializeApp } from '@firebase/app'; import { expect } from 'chai'; -import { ConnectorConfig, getDataConnect } from '../../src'; +import { getDataConnect } from '../../src'; describe('Data Connect Test', () => { + beforeEach(() => {}); it('should throw an error if `projectId` is not provided', async () => { const app = initializeApp({ projectId: undefined }, 'a'); expect(() => @@ -30,24 +31,33 @@ describe('Data Connect Test', () => { ); await deleteApp(app); }); - it('should not throw an error if `projectId` is provided', () => { + it('should not throw an error if `projectId` is provided', async () => { const projectId = 'p'; - initializeApp({ projectId }); + const customApp = initializeApp({ projectId }, 'customApp'); expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's' }) ).to.not.throw( 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' ); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + const dc = getDataConnect(customApp, { + connector: 'c', + location: 'l', + service: 's' + }); expect(dc.app.options.projectId).to.eq(projectId); + await deleteApp(customApp); }); - it('should throw an error if `connectorConfig` is not provided', () => { + it('should throw an error if `connectorConfig` is not provided', async () => { const projectId = 'p'; - initializeApp({ projectId }); - expect(() => getDataConnect({} as ConnectorConfig)).to.throw( - 'DC Option Required' - ); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + const customApp = initializeApp({ projectId }, 'customApp'); + // @ts-ignore + expect(() => getDataConnect(customApp)).to.throw('DC Option Required'); + const dc = getDataConnect(customApp, { + connector: 'c', + location: 'l', + service: 's' + }); expect(dc.app.options.projectId).to.eq(projectId); + await deleteApp(customApp); }); }); diff --git a/packages/data-connect/test/unit/transportoptions.test.ts b/packages/data-connect/test/unit/transportoptions.test.ts index 91e090e6a54..a7136b5c408 100644 --- a/packages/data-connect/test/unit/transportoptions.test.ts +++ b/packages/data-connect/test/unit/transportoptions.test.ts @@ -16,6 +16,8 @@ */ import { expect } from 'chai'; + +import { queryRef } from '../../src'; import { TransportOptions, areTransportOptionsEqual, @@ -23,7 +25,6 @@ import { getDataConnect } from '../../src/api/DataConnect'; import { app } from '../util'; -import { queryRef } from '../../src'; describe('Transport Options', () => { it('should return false if transport options are not equal', () => { const transportOptions1: TransportOptions = { diff --git a/packages/data-connect/test/unit/utils.test.ts b/packages/data-connect/test/unit/utils.test.ts index c69c1c8f511..666ca04ac3d 100644 --- a/packages/data-connect/test/unit/utils.test.ts +++ b/packages/data-connect/test/unit/utils.test.ts @@ -19,6 +19,7 @@ import { expect } from 'chai'; import { getDataConnect } from '../../src'; import { validateArgs } from '../../src/util/validateArgs'; +import { app } from '../util'; describe('Utils', () => { it('[Vars required: true] should throw if no arguments are provided', () => { const connectorConfig = { connector: 'c', location: 'l', service: 's' }; @@ -28,12 +29,12 @@ describe('Utils', () => { }); it('[vars required: false, vars provided: false] should return data connect instance and no variables', () => { const connectorConfig = { connector: 'c', location: 'l', service: 's' }; - const dc = getDataConnect(connectorConfig); + const dc = getDataConnect(app, connectorConfig); expect(validateArgs(connectorConfig)).to.deep.eq({ dc, vars: undefined }); }); it('[vars required: false, vars provided: false, data connect provided: true] should return data connect instance and no variables', () => { const connectorConfig = { connector: 'c', location: 'l', service: 's' }; - const dc = getDataConnect(connectorConfig); + const dc = getDataConnect(app, connectorConfig); expect(validateArgs(connectorConfig, dc)).to.deep.eq({ dc, vars: undefined @@ -41,7 +42,7 @@ describe('Utils', () => { }); it('[vars required: true, vars provided: true, data connect provided: true] should return data connect instance and variables', () => { const connectorConfig = { connector: 'c', location: 'l', service: 's' }; - const dc = getDataConnect(connectorConfig); + const dc = getDataConnect(app, connectorConfig); const vars = { a: 1 }; expect(validateArgs(connectorConfig, dc, vars)).to.deep.eq({ dc, vars }); }); diff --git a/packages/data-connect/test/util.ts b/packages/data-connect/test/util.ts index cd9149ed41e..625c263c311 100644 --- a/packages/data-connect/test/util.ts +++ b/packages/data-connect/test/util.ts @@ -24,13 +24,12 @@ import { getDataConnect } from '../src'; -export const USE_EMULATOR = true; export const EMULATOR_PORT = process.env.DC_EMULATOR_PORT; -// export const EMULATOR_PROJECT = process.env.PROJECT; -export const CONNECTOR_NAME = 'c'; -export const LOCATION_NAME = 'l'; -export const SERVICE_NAME = 'l'; -export const PROJECT_ID = 'p'; +const USE_EMULATOR = !!EMULATOR_PORT; +export const CONNECTOR_NAME = 'tests'; +export const LOCATION_NAME = 'us-west2'; +export const SERVICE_NAME = 'fdc-service'; +export const PROJECT_ID = USE_EMULATOR ? 'p' : 'jscore-sandbox-141b5'; export function getConnectionConfig(): ConnectorConfig { return { connector: CONNECTOR_NAME, @@ -46,7 +45,7 @@ export const app = initializeApp({ // Seed the database to have the proper fields to query, such as a list of tasks. export function initDatabase(): DataConnect { const instance = getDataConnect(getConnectionConfig()); - if (!instance.isEmulator) { + if (USE_EMULATOR) { connectDataConnectEmulator(instance, 'localhost', Number(EMULATOR_PORT)); } return instance; diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index efe5bdbe52c..5cea83b073b 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -18,26 +18,21 @@ import { platform } from 'os'; import { Emulator } from './emulator'; -const DATABASE_EMULATOR_VERSION = '1.3.7'; +const DATACONNECT_EMULATOR_VERSION = '1.7.5'; export class DataConnectEmulator extends Emulator { - // namespace: string; - - constructor(port = 3628) { + constructor(port = 9399) { const os = platform(); let urlString = ''; switch (os) { case 'darwin': - urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.7?alt=media&token=2cf32435-d479-4929-b963-a97ae1ac3f0b'; + urlString = `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v${DATACONNECT_EMULATOR_VERSION}?alt=media`; break; case 'linux': - urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.7?alt=media&token=fd33b4fc-2e27-4874-893a-2d1f0ecbf116'; + urlString = `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v${DATACONNECT_EMULATOR_VERSION}?alt=media`; break; case 'win32': - urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.7?alt=media&token=bd6e60b0-50b4-46db-aa6c-5fcc6e991f39'; + urlString = `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v${DATACONNECT_EMULATOR_VERSION}?alt=media`; break; default: throw new Error( @@ -45,7 +40,7 @@ export class DataConnectEmulator extends Emulator { ); } super( - `cli-v${DATABASE_EMULATOR_VERSION}`, + `cli-v${DATACONNECT_EMULATOR_VERSION}`, // Use locked version of emulator for test to be deterministic. // The latest version can be found from database emulator doc: // https://firebase.google.com/docs/database/security/test-rules-emulator @@ -54,4 +49,13 @@ export class DataConnectEmulator extends Emulator { ); this.isDataConnect = true; } + async setUp(): Promise { + await super.setUp(); + await fetch(`http://localhost:${this.port}/emulator/configure`, { + method: 'POST', + body: JSON.stringify({ + 'use_dummy': true + }) + }); + } } diff --git a/scripts/emulator-testing/emulators/emulator.ts b/scripts/emulator-testing/emulators/emulator.ts index f149debca14..0eeb1ca88bd 100644 --- a/scripts/emulator-testing/emulators/emulator.ts +++ b/scripts/emulator-testing/emulators/emulator.ts @@ -123,6 +123,20 @@ export abstract class Emulator { }); } + findDataConnectConfigDir() { + let path = './'; + const files = fs.readdirSync(path); + if (files.includes('dataconnect')) { + return path + 'dataconnect'; + } + if (files.includes('test')) { + return path + 'test/dataconnect'; + } + throw new Error( + 'Max Depth Exceeded. Please run from the data-connect/test folder' + ); + } + setUp(): Promise { return new Promise((resolve, reject) => { if (!this.binaryPath) { @@ -130,11 +144,17 @@ export abstract class Emulator { } let promise: ChildProcessPromise; if (this.isDataConnect) { + const dataConnectConfigDir = this.findDataConnectConfigDir(); promise = spawn(this.binaryPath, [ + '--v=2', 'dev', - '--local_connection_string', - "'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable'" + `--listen=127.0.0.1:${this.port},[::1]:${this.port}`, + `--config_dir=${dataConnectConfigDir}` ]); + promise.childProcess.stdout?.on('data', console.log); + promise.childProcess.stderr?.on('data', res => + console.log(res.toString()) + ); } else { promise = spawn( 'java',