#
tokens: 45105/50000 1/935 files (page 43/44)
lines: off (toggle) GitHub
raw markdown copy
This is page 43 of 44. Use http://codebase.md/googleapis/genai-toolbox?page={x} to view the full context.

# Directory Structure

```
├── .ci
│   ├── continuous.release.cloudbuild.yaml
│   ├── generate_release_table.sh
│   ├── integration.cloudbuild.yaml
│   ├── quickstart_test
│   │   ├── go.integration.cloudbuild.yaml
│   │   ├── js.integration.cloudbuild.yaml
│   │   ├── py.integration.cloudbuild.yaml
│   │   ├── run_go_tests.sh
│   │   ├── run_js_tests.sh
│   │   ├── run_py_tests.sh
│   │   └── setup_hotels_sample.sql
│   ├── test_with_coverage.sh
│   └── versioned.release.cloudbuild.yaml
├── .github
│   ├── auto-label.yaml
│   ├── blunderbuss.yml
│   ├── CODEOWNERS
│   ├── header-checker-lint.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── question.yml
│   ├── label-sync.yml
│   ├── labels.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── release-please.yml
│   ├── renovate.json5
│   ├── sync-repo-settings.yaml
│   └── workflows
│       ├── cloud_build_failure_reporter.yml
│       ├── deploy_dev_docs.yaml
│       ├── deploy_previous_version_docs.yaml
│       ├── deploy_versioned_docs.yaml
│       ├── docs_deploy.yaml
│       ├── docs_preview_clean.yaml
│       ├── docs_preview_deploy.yaml
│       ├── lint.yaml
│       ├── schedule_reporter.yml
│       ├── sync-labels.yaml
│       └── tests.yaml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── .hugo
│   ├── archetypes
│   │   └── default.md
│   ├── assets
│   │   ├── icons
│   │   │   └── logo.svg
│   │   └── scss
│   │       ├── _styles_project.scss
│   │       └── _variables_project.scss
│   ├── go.mod
│   ├── go.sum
│   ├── hugo.toml
│   ├── layouts
│   │   ├── _default
│   │   │   └── home.releases.releases
│   │   ├── index.llms-full.txt
│   │   ├── index.llms.txt
│   │   ├── partials
│   │   │   ├── hooks
│   │   │   │   └── head-end.html
│   │   │   ├── navbar-version-selector.html
│   │   │   ├── page-meta-links.html
│   │   │   └── td
│   │   │       └── render-heading.html
│   │   ├── robot.txt
│   │   └── shortcodes
│   │       ├── include.html
│   │       ├── ipynb.html
│   │       └── regionInclude.html
│   ├── package-lock.json
│   ├── package.json
│   └── static
│       ├── favicons
│       │   ├── android-chrome-192x192.png
│       │   ├── android-chrome-512x512.png
│       │   ├── apple-touch-icon.png
│       │   ├── favicon-16x16.png
│       │   ├── favicon-32x32.png
│       │   └── favicon.ico
│       └── js
│           └── w3.js
├── CHANGELOG.md
├── cmd
│   ├── options_test.go
│   ├── options.go
│   ├── root_test.go
│   ├── root.go
│   └── version.txt
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOPER.md
├── Dockerfile
├── docs
│   └── en
│       ├── _index.md
│       ├── about
│       │   ├── _index.md
│       │   └── faq.md
│       ├── concepts
│       │   ├── _index.md
│       │   └── telemetry
│       │       ├── index.md
│       │       ├── telemetry_flow.png
│       │       └── telemetry_traces.png
│       ├── getting-started
│       │   ├── _index.md
│       │   ├── colab_quickstart.ipynb
│       │   ├── configure.md
│       │   ├── introduction
│       │   │   ├── _index.md
│       │   │   └── architecture.png
│       │   ├── local_quickstart_go.md
│       │   ├── local_quickstart_js.md
│       │   ├── local_quickstart.md
│       │   ├── mcp_quickstart
│       │   │   ├── _index.md
│       │   │   ├── inspector_tools.png
│       │   │   └── inspector.png
│       │   └── quickstart
│       │       ├── go
│       │       │   ├── adkgo
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── genAI
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── genkit
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── langchain
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── openAI
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   └── quickstart_test.go
│       │       ├── golden.txt
│       │       ├── js
│       │       │   ├── genAI
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── genkit
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── langchain
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── llamaindex
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   └── quickstart.test.js
│       │       ├── python
│       │       │   ├── __init__.py
│       │       │   ├── adk
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── core
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── langchain
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── llamaindex
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   └── quickstart_test.py
│       │       └── shared
│       │           ├── cloud_setup.md
│       │           ├── configure_toolbox.md
│       │           └── database_setup.md
│       ├── how-to
│       │   ├── _index.md
│       │   ├── connect_via_geminicli.md
│       │   ├── connect_via_mcp.md
│       │   ├── connect-ide
│       │   │   ├── _index.md
│       │   │   ├── alloydb_pg_admin_mcp.md
│       │   │   ├── alloydb_pg_mcp.md
│       │   │   ├── bigquery_mcp.md
│       │   │   ├── cloud_sql_mssql_admin_mcp.md
│       │   │   ├── cloud_sql_mssql_mcp.md
│       │   │   ├── cloud_sql_mysql_admin_mcp.md
│       │   │   ├── cloud_sql_mysql_mcp.md
│       │   │   ├── cloud_sql_pg_admin_mcp.md
│       │   │   ├── cloud_sql_pg_mcp.md
│       │   │   ├── firestore_mcp.md
│       │   │   ├── looker_mcp.md
│       │   │   ├── mssql_mcp.md
│       │   │   ├── mysql_mcp.md
│       │   │   ├── neo4j_mcp.md
│       │   │   ├── postgres_mcp.md
│       │   │   ├── spanner_mcp.md
│       │   │   └── sqlite_mcp.md
│       │   ├── deploy_docker.md
│       │   ├── deploy_gke.md
│       │   ├── deploy_toolbox.md
│       │   ├── export_telemetry.md
│       │   └── toolbox-ui
│       │       ├── edit-headers.gif
│       │       ├── edit-headers.png
│       │       ├── index.md
│       │       ├── optional-param-checked.png
│       │       ├── optional-param-unchecked.png
│       │       ├── run-tool.gif
│       │       ├── tools.png
│       │       └── toolsets.png
│       ├── reference
│       │   ├── _index.md
│       │   ├── cli.md
│       │   └── prebuilt-tools.md
│       ├── resources
│       │   ├── _index.md
│       │   ├── authServices
│       │   │   ├── _index.md
│       │   │   └── google.md
│       │   ├── sources
│       │   │   ├── _index.md
│       │   │   ├── alloydb-admin.md
│       │   │   ├── alloydb-pg.md
│       │   │   ├── bigquery.md
│       │   │   ├── bigtable.md
│       │   │   ├── cassandra.md
│       │   │   ├── clickhouse.md
│       │   │   ├── cloud-healthcare.md
│       │   │   ├── cloud-monitoring.md
│       │   │   ├── cloud-sql-admin.md
│       │   │   ├── cloud-sql-mssql.md
│       │   │   ├── cloud-sql-mysql.md
│       │   │   ├── cloud-sql-pg.md
│       │   │   ├── couchbase.md
│       │   │   ├── dataplex.md
│       │   │   ├── dgraph.md
│       │   │   ├── elasticsearch.md
│       │   │   ├── firebird.md
│       │   │   ├── firestore.md
│       │   │   ├── http.md
│       │   │   ├── looker.md
│       │   │   ├── mindsdb.md
│       │   │   ├── mongodb.md
│       │   │   ├── mssql.md
│       │   │   ├── mysql.md
│       │   │   ├── neo4j.md
│       │   │   ├── oceanbase.md
│       │   │   ├── oracle.md
│       │   │   ├── postgres.md
│       │   │   ├── redis.md
│       │   │   ├── serverless-spark.md
│       │   │   ├── singlestore.md
│       │   │   ├── spanner.md
│       │   │   ├── sqlite.md
│       │   │   ├── tidb.md
│       │   │   ├── trino.md
│       │   │   ├── valkey.md
│       │   │   └── yugabytedb.md
│       │   └── tools
│       │       ├── _index.md
│       │       ├── alloydb
│       │       │   ├── _index.md
│       │       │   ├── alloydb-create-cluster.md
│       │       │   ├── alloydb-create-instance.md
│       │       │   ├── alloydb-create-user.md
│       │       │   ├── alloydb-get-cluster.md
│       │       │   ├── alloydb-get-instance.md
│       │       │   ├── alloydb-get-user.md
│       │       │   ├── alloydb-list-clusters.md
│       │       │   ├── alloydb-list-instances.md
│       │       │   ├── alloydb-list-users.md
│       │       │   └── alloydb-wait-for-operation.md
│       │       ├── alloydbainl
│       │       │   ├── _index.md
│       │       │   └── alloydb-ai-nl.md
│       │       ├── bigquery
│       │       │   ├── _index.md
│       │       │   ├── bigquery-analyze-contribution.md
│       │       │   ├── bigquery-conversational-analytics.md
│       │       │   ├── bigquery-execute-sql.md
│       │       │   ├── bigquery-forecast.md
│       │       │   ├── bigquery-get-dataset-info.md
│       │       │   ├── bigquery-get-table-info.md
│       │       │   ├── bigquery-list-dataset-ids.md
│       │       │   ├── bigquery-list-table-ids.md
│       │       │   ├── bigquery-search-catalog.md
│       │       │   └── bigquery-sql.md
│       │       ├── bigtable
│       │       │   ├── _index.md
│       │       │   └── bigtable-sql.md
│       │       ├── cassandra
│       │       │   ├── _index.md
│       │       │   └── cassandra-cql.md
│       │       ├── clickhouse
│       │       │   ├── _index.md
│       │       │   ├── clickhouse-execute-sql.md
│       │       │   ├── clickhouse-list-databases.md
│       │       │   ├── clickhouse-list-tables.md
│       │       │   └── clickhouse-sql.md
│       │       ├── cloudhealthcare
│       │       │   ├── _index.md
│       │       │   ├── cloud-healthcare-fhir-fetch-page.md
│       │       │   ├── cloud-healthcare-fhir-patient-everything.md
│       │       │   ├── cloud-healthcare-fhir-patient-search.md
│       │       │   ├── cloud-healthcare-get-dataset.md
│       │       │   ├── cloud-healthcare-get-dicom-store-metrics.md
│       │       │   ├── cloud-healthcare-get-dicom-store.md
│       │       │   ├── cloud-healthcare-get-fhir-resource.md
│       │       │   ├── cloud-healthcare-get-fhir-store-metrics.md
│       │       │   ├── cloud-healthcare-get-fhir-store.md
│       │       │   ├── cloud-healthcare-list-dicom-stores.md
│       │       │   ├── cloud-healthcare-list-fhir-stores.md
│       │       │   ├── cloud-healthcare-retrieve-rendered-dicom-instance.md
│       │       │   ├── cloud-healthcare-search-dicom-instances.md
│       │       │   ├── cloud-healthcare-search-dicom-series.md
│       │       │   └── cloud-healthcare-search-dicom-studies.md
│       │       ├── cloudmonitoring
│       │       │   ├── _index.md
│       │       │   └── cloud-monitoring-query-prometheus.md
│       │       ├── cloudsql
│       │       │   ├── _index.md
│       │       │   ├── cloudsqlcreatedatabase.md
│       │       │   ├── cloudsqlcreateusers.md
│       │       │   ├── cloudsqlgetinstances.md
│       │       │   ├── cloudsqllistdatabases.md
│       │       │   ├── cloudsqllistinstances.md
│       │       │   ├── cloudsqlmssqlcreateinstance.md
│       │       │   ├── cloudsqlmysqlcreateinstance.md
│       │       │   ├── cloudsqlpgcreateinstances.md
│       │       │   └── cloudsqlwaitforoperation.md
│       │       ├── couchbase
│       │       │   ├── _index.md
│       │       │   └── couchbase-sql.md
│       │       ├── dataform
│       │       │   ├── _index.md
│       │       │   └── dataform-compile-local.md
│       │       ├── dataplex
│       │       │   ├── _index.md
│       │       │   ├── dataplex-lookup-entry.md
│       │       │   ├── dataplex-search-aspect-types.md
│       │       │   └── dataplex-search-entries.md
│       │       ├── dgraph
│       │       │   ├── _index.md
│       │       │   └── dgraph-dql.md
│       │       ├── elasticsearch
│       │       │   ├── _index.md
│       │       │   └── elasticsearch-esql.md
│       │       ├── firebird
│       │       │   ├── _index.md
│       │       │   ├── firebird-execute-sql.md
│       │       │   └── firebird-sql.md
│       │       ├── firestore
│       │       │   ├── _index.md
│       │       │   ├── firestore-add-documents.md
│       │       │   ├── firestore-delete-documents.md
│       │       │   ├── firestore-get-documents.md
│       │       │   ├── firestore-get-rules.md
│       │       │   ├── firestore-list-collections.md
│       │       │   ├── firestore-query-collection.md
│       │       │   ├── firestore-query.md
│       │       │   ├── firestore-update-document.md
│       │       │   └── firestore-validate-rules.md
│       │       ├── http
│       │       │   ├── _index.md
│       │       │   └── http.md
│       │       ├── looker
│       │       │   ├── _index.md
│       │       │   ├── looker-add-dashboard-element.md
│       │       │   ├── looker-conversational-analytics.md
│       │       │   ├── looker-create-project-file.md
│       │       │   ├── looker-delete-project-file.md
│       │       │   ├── looker-dev-mode.md
│       │       │   ├── looker-get-connection-databases.md
│       │       │   ├── looker-get-connection-schemas.md
│       │       │   ├── looker-get-connection-table-columns.md
│       │       │   ├── looker-get-connection-tables.md
│       │       │   ├── looker-get-connections.md
│       │       │   ├── looker-get-dashboards.md
│       │       │   ├── looker-get-dimensions.md
│       │       │   ├── looker-get-explores.md
│       │       │   ├── looker-get-filters.md
│       │       │   ├── looker-get-looks.md
│       │       │   ├── looker-get-measures.md
│       │       │   ├── looker-get-models.md
│       │       │   ├── looker-get-parameters.md
│       │       │   ├── looker-get-project-file.md
│       │       │   ├── looker-get-project-files.md
│       │       │   ├── looker-get-projects.md
│       │       │   ├── looker-health-analyze.md
│       │       │   ├── looker-health-pulse.md
│       │       │   ├── looker-health-vacuum.md
│       │       │   ├── looker-make-dashboard.md
│       │       │   ├── looker-make-look.md
│       │       │   ├── looker-query-sql.md
│       │       │   ├── looker-query-url.md
│       │       │   ├── looker-query.md
│       │       │   ├── looker-run-dashboard.md
│       │       │   ├── looker-run-look.md
│       │       │   └── looker-update-project-file.md
│       │       ├── mindsdb
│       │       │   ├── _index.md
│       │       │   ├── mindsdb-execute-sql.md
│       │       │   └── mindsdb-sql.md
│       │       ├── mongodb
│       │       │   ├── _index.md
│       │       │   ├── mongodb-aggregate.md
│       │       │   ├── mongodb-delete-many.md
│       │       │   ├── mongodb-delete-one.md
│       │       │   ├── mongodb-find-one.md
│       │       │   ├── mongodb-find.md
│       │       │   ├── mongodb-insert-many.md
│       │       │   ├── mongodb-insert-one.md
│       │       │   ├── mongodb-update-many.md
│       │       │   └── mongodb-update-one.md
│       │       ├── mssql
│       │       │   ├── _index.md
│       │       │   ├── mssql-execute-sql.md
│       │       │   ├── mssql-list-tables.md
│       │       │   └── mssql-sql.md
│       │       ├── mysql
│       │       │   ├── _index.md
│       │       │   ├── mysql-execute-sql.md
│       │       │   ├── mysql-list-active-queries.md
│       │       │   ├── mysql-list-table-fragmentation.md
│       │       │   ├── mysql-list-tables-missing-unique-indexes.md
│       │       │   ├── mysql-list-tables.md
│       │       │   └── mysql-sql.md
│       │       ├── neo4j
│       │       │   ├── _index.md
│       │       │   ├── neo4j-cypher.md
│       │       │   ├── neo4j-execute-cypher.md
│       │       │   └── neo4j-schema.md
│       │       ├── oceanbase
│       │       │   ├── _index.md
│       │       │   ├── oceanbase-execute-sql.md
│       │       │   └── oceanbase-sql.md
│       │       ├── oracle
│       │       │   ├── _index.md
│       │       │   ├── oracle-execute-sql.md
│       │       │   └── oracle-sql.md
│       │       ├── postgres
│       │       │   ├── _index.md
│       │       │   ├── postgres-execute-sql.md
│       │       │   ├── postgres-list-active-queries.md
│       │       │   ├── postgres-list-available-extensions.md
│       │       │   ├── postgres-list-installed-extensions.md
│       │       │   ├── postgres-list-schemas.md
│       │       │   ├── postgres-list-tables.md
│       │       │   ├── postgres-list-views.md
│       │       │   └── postgres-sql.md
│       │       ├── redis
│       │       │   ├── _index.md
│       │       │   └── redis.md
│       │       ├── serverless-spark
│       │       │   ├── _index.md
│       │       │   ├── serverless-spark-cancel-batch.md
│       │       │   ├── serverless-spark-get-batch.md
│       │       │   └── serverless-spark-list-batches.md
│       │       ├── singlestore
│       │       │   ├── _index.md
│       │       │   ├── singlestore-execute-sql.md
│       │       │   └── singlestore-sql.md
│       │       ├── spanner
│       │       │   ├── _index.md
│       │       │   ├── spanner-execute-sql.md
│       │       │   ├── spanner-list-tables.md
│       │       │   └── spanner-sql.md
│       │       ├── sqlite
│       │       │   ├── _index.md
│       │       │   ├── sqlite-execute-sql.md
│       │       │   └── sqlite-sql.md
│       │       ├── tidb
│       │       │   ├── _index.md
│       │       │   ├── tidb-execute-sql.md
│       │       │   └── tidb-sql.md
│       │       ├── trino
│       │       │   ├── _index.md
│       │       │   ├── trino-execute-sql.md
│       │       │   └── trino-sql.md
│       │       ├── utility
│       │       │   ├── _index.md
│       │       │   └── wait.md
│       │       ├── valkey
│       │       │   ├── _index.md
│       │       │   └── valkey.md
│       │       └── yuagbytedb
│       │           ├── _index.md
│       │           └── yugabytedb-sql.md
│       ├── samples
│       │   ├── _index.md
│       │   ├── alloydb
│       │   │   ├── _index.md
│       │   │   ├── ai-nl
│       │   │   │   ├── alloydb_ai_nl.ipynb
│       │   │   │   └── index.md
│       │   │   └── mcp_quickstart.md
│       │   ├── bigquery
│       │   │   ├── _index.md
│       │   │   ├── colab_quickstart_bigquery.ipynb
│       │   │   ├── local_quickstart.md
│       │   │   └── mcp_quickstart
│       │   │       ├── _index.md
│       │   │       ├── inspector_tools.png
│       │   │       └── inspector.png
│       │   └── looker
│       │       ├── _index.md
│       │       ├── looker_gemini_oauth
│       │       │   ├── _index.md
│       │       │   ├── authenticated.png
│       │       │   ├── authorize.png
│       │       │   └── registration.png
│       │       ├── looker_gemini.md
│       │       └── looker_mcp_inspector
│       │           ├── _index.md
│       │           ├── inspector_tools.png
│       │           └── inspector.png
│       └── sdks
│           ├── _index.md
│           ├── go-sdk.md
│           ├── js-sdk.md
│           └── python-sdk.md
├── gemini-extension.json
├── go.mod
├── go.sum
├── internal
│   ├── auth
│   │   ├── auth.go
│   │   └── google
│   │       └── google.go
│   ├── log
│   │   ├── handler.go
│   │   ├── log_test.go
│   │   ├── log.go
│   │   └── logger.go
│   ├── prebuiltconfigs
│   │   ├── prebuiltconfigs_test.go
│   │   ├── prebuiltconfigs.go
│   │   └── tools
│   │       ├── alloydb-postgres-admin.yaml
│   │       ├── alloydb-postgres-observability.yaml
│   │       ├── alloydb-postgres.yaml
│   │       ├── bigquery.yaml
│   │       ├── clickhouse.yaml
│   │       ├── cloud-healthcare.yaml
│   │       ├── cloud-sql-mssql-admin.yaml
│   │       ├── cloud-sql-mssql-observability.yaml
│   │       ├── cloud-sql-mssql.yaml
│   │       ├── cloud-sql-mysql-admin.yaml
│   │       ├── cloud-sql-mysql-observability.yaml
│   │       ├── cloud-sql-mysql.yaml
│   │       ├── cloud-sql-postgres-admin.yaml
│   │       ├── cloud-sql-postgres-observability.yaml
│   │       ├── cloud-sql-postgres.yaml
│   │       ├── dataplex.yaml
│   │       ├── elasticsearch.yaml
│   │       ├── firestore.yaml
│   │       ├── looker-conversational-analytics.yaml
│   │       ├── looker.yaml
│   │       ├── mindsdb.yaml
│   │       ├── mssql.yaml
│   │       ├── mysql.yaml
│   │       ├── neo4j.yaml
│   │       ├── oceanbase.yaml
│   │       ├── postgres.yaml
│   │       ├── serverless-spark.yaml
│   │       ├── singlestore.yaml
│   │       ├── spanner-postgres.yaml
│   │       ├── spanner.yaml
│   │       └── sqlite.yaml
│   ├── server
│   │   ├── api_test.go
│   │   ├── api.go
│   │   ├── common_test.go
│   │   ├── config.go
│   │   ├── mcp
│   │   │   ├── jsonrpc
│   │   │   │   ├── jsonrpc_test.go
│   │   │   │   └── jsonrpc.go
│   │   │   ├── mcp.go
│   │   │   ├── util
│   │   │   │   └── lifecycle.go
│   │   │   ├── v20241105
│   │   │   │   ├── method.go
│   │   │   │   └── types.go
│   │   │   ├── v20250326
│   │   │   │   ├── method.go
│   │   │   │   └── types.go
│   │   │   └── v20250618
│   │   │       ├── method.go
│   │   │       └── types.go
│   │   ├── mcp_test.go
│   │   ├── mcp.go
│   │   ├── server_test.go
│   │   ├── server.go
│   │   ├── static
│   │   │   ├── assets
│   │   │   │   └── mcptoolboxlogo.png
│   │   │   ├── css
│   │   │   │   └── style.css
│   │   │   ├── index.html
│   │   │   ├── js
│   │   │   │   ├── auth.js
│   │   │   │   ├── loadTools.js
│   │   │   │   ├── mainContent.js
│   │   │   │   ├── navbar.js
│   │   │   │   ├── runTool.js
│   │   │   │   ├── toolDisplay.js
│   │   │   │   ├── tools.js
│   │   │   │   └── toolsets.js
│   │   │   ├── tools.html
│   │   │   └── toolsets.html
│   │   ├── web_test.go
│   │   └── web.go
│   ├── sources
│   │   ├── alloydbadmin
│   │   │   ├── alloydbadmin_test.go
│   │   │   └── alloydbadmin.go
│   │   ├── alloydbpg
│   │   │   ├── alloydb_pg_test.go
│   │   │   └── alloydb_pg.go
│   │   ├── bigquery
│   │   │   ├── bigquery_test.go
│   │   │   ├── bigquery.go
│   │   │   └── cache.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   ├── cassandra_test.go
│   │   │   └── cassandra.go
│   │   ├── clickhouse
│   │   │   ├── clickhouse_test.go
│   │   │   └── clickhouse.go
│   │   ├── cloudhealthcare
│   │   │   ├── cloud_healthcare_test.go
│   │   │   └── cloud_healthcare.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloud_monitoring_test.go
│   │   │   └── cloud_monitoring.go
│   │   ├── cloudsqladmin
│   │   │   ├── cloud_sql_admin_test.go
│   │   │   └── cloud_sql_admin.go
│   │   ├── cloudsqlmssql
│   │   │   ├── cloud_sql_mssql_test.go
│   │   │   └── cloud_sql_mssql.go
│   │   ├── cloudsqlmysql
│   │   │   ├── cloud_sql_mysql_test.go
│   │   │   └── cloud_sql_mysql.go
│   │   ├── cloudsqlpg
│   │   │   ├── cloud_sql_pg_test.go
│   │   │   └── cloud_sql_pg.go
│   │   ├── couchbase
│   │   │   ├── couchbase_test.go
│   │   │   └── couchbase.go
│   │   ├── dataplex
│   │   │   ├── dataplex_test.go
│   │   │   └── dataplex.go
│   │   ├── dgraph
│   │   │   ├── dgraph_test.go
│   │   │   └── dgraph.go
│   │   ├── dialect.go
│   │   ├── elasticsearch
│   │   │   ├── elasticsearch_test.go
│   │   │   └── elasticsearch.go
│   │   ├── firebird
│   │   │   ├── firebird_test.go
│   │   │   └── firebird.go
│   │   ├── firestore
│   │   │   ├── firestore_test.go
│   │   │   └── firestore.go
│   │   ├── http
│   │   │   ├── http_test.go
│   │   │   └── http.go
│   │   ├── ip_type.go
│   │   ├── looker
│   │   │   ├── looker_test.go
│   │   │   └── looker.go
│   │   ├── mindsdb
│   │   │   ├── mindsdb_test.go
│   │   │   └── mindsdb.go
│   │   ├── mongodb
│   │   │   ├── mongodb_test.go
│   │   │   └── mongodb.go
│   │   ├── mssql
│   │   │   ├── mssql_test.go
│   │   │   └── mssql.go
│   │   ├── mysql
│   │   │   ├── mysql_test.go
│   │   │   └── mysql.go
│   │   ├── neo4j
│   │   │   ├── neo4j_test.go
│   │   │   └── neo4j.go
│   │   ├── oceanbase
│   │   │   ├── oceanbase_test.go
│   │   │   └── oceanbase.go
│   │   ├── oracle
│   │   │   └── oracle.go
│   │   ├── postgres
│   │   │   ├── postgres_test.go
│   │   │   └── postgres.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── serverlessspark
│   │   │   ├── serverlessspark_test.go
│   │   │   └── serverlessspark.go
│   │   ├── singlestore
│   │   │   ├── singlestore_test.go
│   │   │   └── singlestore.go
│   │   ├── sources.go
│   │   ├── spanner
│   │   │   ├── spanner_test.go
│   │   │   └── spanner.go
│   │   ├── sqlite
│   │   │   ├── sqlite_test.go
│   │   │   └── sqlite.go
│   │   ├── tidb
│   │   │   ├── tidb_test.go
│   │   │   └── tidb.go
│   │   ├── trino
│   │   │   ├── trino_test.go
│   │   │   └── trino.go
│   │   ├── util.go
│   │   ├── valkey
│   │   │   ├── valkey_test.go
│   │   │   └── valkey.go
│   │   └── yugabytedb
│   │       ├── yugabytedb_test.go
│   │       └── yugabytedb.go
│   ├── telemetry
│   │   ├── instrumentation.go
│   │   └── telemetry.go
│   ├── testutils
│   │   └── testutils.go
│   ├── tools
│   │   ├── alloydb
│   │   │   ├── alloydbcreatecluster
│   │   │   │   ├── alloydbcreatecluster_test.go
│   │   │   │   └── alloydbcreatecluster.go
│   │   │   ├── alloydbcreateinstance
│   │   │   │   ├── alloydbcreateinstance_test.go
│   │   │   │   └── alloydbcreateinstance.go
│   │   │   ├── alloydbcreateuser
│   │   │   │   ├── alloydbcreateuser_test.go
│   │   │   │   └── alloydbcreateuser.go
│   │   │   ├── alloydbgetcluster
│   │   │   │   ├── alloydbgetcluster_test.go
│   │   │   │   └── alloydbgetcluster.go
│   │   │   ├── alloydbgetinstance
│   │   │   │   ├── alloydbgetinstance_test.go
│   │   │   │   └── alloydbgetinstance.go
│   │   │   ├── alloydbgetuser
│   │   │   │   ├── alloydbgetuser_test.go
│   │   │   │   └── alloydbgetuser.go
│   │   │   ├── alloydblistclusters
│   │   │   │   ├── alloydblistclusters_test.go
│   │   │   │   └── alloydblistclusters.go
│   │   │   ├── alloydblistinstances
│   │   │   │   ├── alloydblistinstances_test.go
│   │   │   │   └── alloydblistinstances.go
│   │   │   ├── alloydblistusers
│   │   │   │   ├── alloydblistusers_test.go
│   │   │   │   └── alloydblistusers.go
│   │   │   └── alloydbwaitforoperation
│   │   │       ├── alloydbwaitforoperation_test.go
│   │   │       └── alloydbwaitforoperation.go
│   │   ├── alloydbainl
│   │   │   ├── alloydbainl_test.go
│   │   │   └── alloydbainl.go
│   │   ├── bigquery
│   │   │   ├── bigqueryanalyzecontribution
│   │   │   │   ├── bigqueryanalyzecontribution_test.go
│   │   │   │   └── bigqueryanalyzecontribution.go
│   │   │   ├── bigquerycommon
│   │   │   │   ├── table_name_parser_test.go
│   │   │   │   ├── table_name_parser.go
│   │   │   │   └── util.go
│   │   │   ├── bigqueryconversationalanalytics
│   │   │   │   ├── bigqueryconversationalanalytics_test.go
│   │   │   │   └── bigqueryconversationalanalytics.go
│   │   │   ├── bigqueryexecutesql
│   │   │   │   ├── bigqueryexecutesql_test.go
│   │   │   │   └── bigqueryexecutesql.go
│   │   │   ├── bigqueryforecast
│   │   │   │   ├── bigqueryforecast_test.go
│   │   │   │   └── bigqueryforecast.go
│   │   │   ├── bigquerygetdatasetinfo
│   │   │   │   ├── bigquerygetdatasetinfo_test.go
│   │   │   │   └── bigquerygetdatasetinfo.go
│   │   │   ├── bigquerygettableinfo
│   │   │   │   ├── bigquerygettableinfo_test.go
│   │   │   │   └── bigquerygettableinfo.go
│   │   │   ├── bigquerylistdatasetids
│   │   │   │   ├── bigquerylistdatasetids_test.go
│   │   │   │   └── bigquerylistdatasetids.go
│   │   │   ├── bigquerylisttableids
│   │   │   │   ├── bigquerylisttableids_test.go
│   │   │   │   └── bigquerylisttableids.go
│   │   │   ├── bigquerysearchcatalog
│   │   │   │   ├── bigquerysearchcatalog_test.go
│   │   │   │   └── bigquerysearchcatalog.go
│   │   │   └── bigquerysql
│   │   │       ├── bigquerysql_test.go
│   │   │       └── bigquerysql.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   └── cassandracql
│   │   │       ├── cassandracql_test.go
│   │   │       └── cassandracql.go
│   │   ├── clickhouse
│   │   │   ├── clickhouseexecutesql
│   │   │   │   ├── clickhouseexecutesql_test.go
│   │   │   │   └── clickhouseexecutesql.go
│   │   │   ├── clickhouselistdatabases
│   │   │   │   ├── clickhouselistdatabases_test.go
│   │   │   │   └── clickhouselistdatabases.go
│   │   │   ├── clickhouselisttables
│   │   │   │   ├── clickhouselisttables_test.go
│   │   │   │   └── clickhouselisttables.go
│   │   │   └── clickhousesql
│   │   │       ├── clickhousesql_test.go
│   │   │       └── clickhousesql.go
│   │   ├── cloudhealthcare
│   │   │   ├── cloudhealthcarefhirfetchpage
│   │   │   │   ├── cloudhealthcarefhirfetchpage_test.go
│   │   │   │   └── cloudhealthcarefhirfetchpage.go
│   │   │   ├── cloudhealthcarefhirpatienteverything
│   │   │   │   ├── cloudhealthcarefhirpatienteverything_test.go
│   │   │   │   └── cloudhealthcarefhirpatienteverything.go
│   │   │   ├── cloudhealthcarefhirpatientsearch
│   │   │   │   ├── cloudhealthcarefhirpatientsearch_test.go
│   │   │   │   └── cloudhealthcarefhirpatientsearch.go
│   │   │   ├── cloudhealthcaregetdataset
│   │   │   │   ├── cloudhealthcaregetdataset_test.go
│   │   │   │   └── cloudhealthcaregetdataset.go
│   │   │   ├── cloudhealthcaregetdicomstore
│   │   │   │   ├── cloudhealthcaregetdicomstore_test.go
│   │   │   │   └── cloudhealthcaregetdicomstore.go
│   │   │   ├── cloudhealthcaregetdicomstoremetrics
│   │   │   │   ├── cloudhealthcaregetdicomstoremetrics_test.go
│   │   │   │   └── cloudhealthcaregetdicomstoremetrics.go
│   │   │   ├── cloudhealthcaregetfhirresource
│   │   │   │   ├── cloudhealthcaregetfhirresource_test.go
│   │   │   │   └── cloudhealthcaregetfhirresource.go
│   │   │   ├── cloudhealthcaregetfhirstore
│   │   │   │   ├── cloudhealthcaregetfhirstore_test.go
│   │   │   │   └── cloudhealthcaregetfhirstore.go
│   │   │   ├── cloudhealthcaregetfhirstoremetrics
│   │   │   │   ├── cloudhealthcaregetfhirstoremetrics_test.go
│   │   │   │   └── cloudhealthcaregetfhirstoremetrics.go
│   │   │   ├── cloudhealthcarelistdicomstores
│   │   │   │   ├── cloudhealthcarelistdicomstores_test.go
│   │   │   │   └── cloudhealthcarelistdicomstores.go
│   │   │   ├── cloudhealthcarelistfhirstores
│   │   │   │   ├── cloudhealthcarelistfhirstores_test.go
│   │   │   │   └── cloudhealthcarelistfhirstores.go
│   │   │   ├── cloudhealthcareretrieverendereddicominstance
│   │   │   │   ├── cloudhealthcareretrieverendereddicominstance_test.go
│   │   │   │   └── cloudhealthcareretrieverendereddicominstance.go
│   │   │   ├── cloudhealthcaresearchdicominstances
│   │   │   │   ├── cloudhealthcaresearchdicominstances_test.go
│   │   │   │   └── cloudhealthcaresearchdicominstances.go
│   │   │   ├── cloudhealthcaresearchdicomseries
│   │   │   │   ├── cloudhealthcaresearchdicomseries_test.go
│   │   │   │   └── cloudhealthcaresearchdicomseries.go
│   │   │   ├── cloudhealthcaresearchdicomstudies
│   │   │   │   ├── cloudhealthcaresearchdicomstudies_test.go
│   │   │   │   └── cloudhealthcaresearchdicomstudies.go
│   │   │   └── common
│   │   │       └── util.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloudmonitoring_test.go
│   │   │   └── cloudmonitoring.go
│   │   ├── cloudsql
│   │   │   ├── cloudsqlcreatedatabase
│   │   │   │   ├── cloudsqlcreatedatabase_test.go
│   │   │   │   └── cloudsqlcreatedatabase.go
│   │   │   ├── cloudsqlcreateusers
│   │   │   │   ├── cloudsqlcreateusers_test.go
│   │   │   │   └── cloudsqlcreateusers.go
│   │   │   ├── cloudsqlgetinstances
│   │   │   │   ├── cloudsqlgetinstances_test.go
│   │   │   │   └── cloudsqlgetinstances.go
│   │   │   ├── cloudsqllistdatabases
│   │   │   │   ├── cloudsqllistdatabases_test.go
│   │   │   │   └── cloudsqllistdatabases.go
│   │   │   ├── cloudsqllistinstances
│   │   │   │   ├── cloudsqllistinstances_test.go
│   │   │   │   └── cloudsqllistinstances.go
│   │   │   └── cloudsqlwaitforoperation
│   │   │       ├── cloudsqlwaitforoperation_test.go
│   │   │       └── cloudsqlwaitforoperation.go
│   │   ├── cloudsqlmssql
│   │   │   └── cloudsqlmssqlcreateinstance
│   │   │       ├── cloudsqlmssqlcreateinstance_test.go
│   │   │       └── cloudsqlmssqlcreateinstance.go
│   │   ├── cloudsqlmysql
│   │   │   └── cloudsqlmysqlcreateinstance
│   │   │       ├── cloudsqlmysqlcreateinstance_test.go
│   │   │       └── cloudsqlmysqlcreateinstance.go
│   │   ├── cloudsqlpg
│   │   │   └── cloudsqlpgcreateinstances
│   │   │       ├── cloudsqlpgcreateinstances_test.go
│   │   │       └── cloudsqlpgcreateinstances.go
│   │   ├── common_test.go
│   │   ├── common.go
│   │   ├── couchbase
│   │   │   ├── couchbase_test.go
│   │   │   └── couchbase.go
│   │   ├── dataform
│   │   │   └── dataformcompilelocal
│   │   │       ├── dataformcompilelocal_test.go
│   │   │       └── dataformcompilelocal.go
│   │   ├── dataplex
│   │   │   ├── dataplexlookupentry
│   │   │   │   ├── dataplexlookupentry_test.go
│   │   │   │   └── dataplexlookupentry.go
│   │   │   ├── dataplexsearchaspecttypes
│   │   │   │   ├── dataplexsearchaspecttypes_test.go
│   │   │   │   └── dataplexsearchaspecttypes.go
│   │   │   └── dataplexsearchentries
│   │   │       ├── dataplexsearchentries_test.go
│   │   │       └── dataplexsearchentries.go
│   │   ├── dgraph
│   │   │   ├── dgraph_test.go
│   │   │   └── dgraph.go
│   │   ├── elasticsearch
│   │   │   └── elasticsearchesql
│   │   │       ├── elasticsearchesql_test.go
│   │   │       └── elasticsearchesql.go
│   │   ├── firebird
│   │   │   ├── firebirdexecutesql
│   │   │   │   ├── firebirdexecutesql_test.go
│   │   │   │   └── firebirdexecutesql.go
│   │   │   └── firebirdsql
│   │   │       ├── firebirdsql_test.go
│   │   │       └── firebirdsql.go
│   │   ├── firestore
│   │   │   ├── firestoreadddocuments
│   │   │   │   ├── firestoreadddocuments_test.go
│   │   │   │   └── firestoreadddocuments.go
│   │   │   ├── firestoredeletedocuments
│   │   │   │   ├── firestoredeletedocuments_test.go
│   │   │   │   └── firestoredeletedocuments.go
│   │   │   ├── firestoregetdocuments
│   │   │   │   ├── firestoregetdocuments_test.go
│   │   │   │   └── firestoregetdocuments.go
│   │   │   ├── firestoregetrules
│   │   │   │   ├── firestoregetrules_test.go
│   │   │   │   └── firestoregetrules.go
│   │   │   ├── firestorelistcollections
│   │   │   │   ├── firestorelistcollections_test.go
│   │   │   │   └── firestorelistcollections.go
│   │   │   ├── firestorequery
│   │   │   │   ├── firestorequery_test.go
│   │   │   │   └── firestorequery.go
│   │   │   ├── firestorequerycollection
│   │   │   │   ├── firestorequerycollection_test.go
│   │   │   │   └── firestorequerycollection.go
│   │   │   ├── firestoreupdatedocument
│   │   │   │   ├── firestoreupdatedocument_test.go
│   │   │   │   └── firestoreupdatedocument.go
│   │   │   ├── firestorevalidaterules
│   │   │   │   ├── firestorevalidaterules_test.go
│   │   │   │   └── firestorevalidaterules.go
│   │   │   └── util
│   │   │       ├── converter_test.go
│   │   │       ├── converter.go
│   │   │       ├── validator_test.go
│   │   │       └── validator.go
│   │   ├── http
│   │   │   ├── http_test.go
│   │   │   └── http.go
│   │   ├── http_method.go
│   │   ├── looker
│   │   │   ├── lookeradddashboardelement
│   │   │   │   ├── lookeradddashboardelement_test.go
│   │   │   │   └── lookeradddashboardelement.go
│   │   │   ├── lookercommon
│   │   │   │   ├── lookercommon_test.go
│   │   │   │   └── lookercommon.go
│   │   │   ├── lookerconversationalanalytics
│   │   │   │   ├── lookerconversationalanalytics_test.go
│   │   │   │   └── lookerconversationalanalytics.go
│   │   │   ├── lookercreateprojectfile
│   │   │   │   ├── lookercreateprojectfile_test.go
│   │   │   │   └── lookercreateprojectfile.go
│   │   │   ├── lookerdeleteprojectfile
│   │   │   │   ├── lookerdeleteprojectfile_test.go
│   │   │   │   └── lookerdeleteprojectfile.go
│   │   │   ├── lookerdevmode
│   │   │   │   ├── lookerdevmode_test.go
│   │   │   │   └── lookerdevmode.go
│   │   │   ├── lookergetconnectiondatabases
│   │   │   │   ├── lookergetconnectiondatabases_test.go
│   │   │   │   └── lookergetconnectiondatabases.go
│   │   │   ├── lookergetconnections
│   │   │   │   ├── lookergetconnections_test.go
│   │   │   │   └── lookergetconnections.go
│   │   │   ├── lookergetconnectionschemas
│   │   │   │   ├── lookergetconnectionschemas_test.go
│   │   │   │   └── lookergetconnectionschemas.go
│   │   │   ├── lookergetconnectiontablecolumns
│   │   │   │   ├── lookergetconnectiontablecolumns_test.go
│   │   │   │   └── lookergetconnectiontablecolumns.go
│   │   │   ├── lookergetconnectiontables
│   │   │   │   ├── lookergetconnectiontables_test.go
│   │   │   │   └── lookergetconnectiontables.go
│   │   │   ├── lookergetdashboards
│   │   │   │   ├── lookergetdashboards_test.go
│   │   │   │   └── lookergetdashboards.go
│   │   │   ├── lookergetdimensions
│   │   │   │   ├── lookergetdimensions_test.go
│   │   │   │   └── lookergetdimensions.go
│   │   │   ├── lookergetexplores
│   │   │   │   ├── lookergetexplores_test.go
│   │   │   │   └── lookergetexplores.go
│   │   │   ├── lookergetfilters
│   │   │   │   ├── lookergetfilters_test.go
│   │   │   │   └── lookergetfilters.go
│   │   │   ├── lookergetlooks
│   │   │   │   ├── lookergetlooks_test.go
│   │   │   │   └── lookergetlooks.go
│   │   │   ├── lookergetmeasures
│   │   │   │   ├── lookergetmeasures_test.go
│   │   │   │   └── lookergetmeasures.go
│   │   │   ├── lookergetmodels
│   │   │   │   ├── lookergetmodels_test.go
│   │   │   │   └── lookergetmodels.go
│   │   │   ├── lookergetparameters
│   │   │   │   ├── lookergetparameters_test.go
│   │   │   │   └── lookergetparameters.go
│   │   │   ├── lookergetprojectfile
│   │   │   │   ├── lookergetprojectfile_test.go
│   │   │   │   └── lookergetprojectfile.go
│   │   │   ├── lookergetprojectfiles
│   │   │   │   ├── lookergetprojectfiles_test.go
│   │   │   │   └── lookergetprojectfiles.go
│   │   │   ├── lookergetprojects
│   │   │   │   ├── lookergetprojects_test.go
│   │   │   │   └── lookergetprojects.go
│   │   │   ├── lookerhealthanalyze
│   │   │   │   ├── lookerhealthanalyze_test.go
│   │   │   │   └── lookerhealthanalyze.go
│   │   │   ├── lookerhealthpulse
│   │   │   │   ├── lookerhealthpulse_test.go
│   │   │   │   └── lookerhealthpulse.go
│   │   │   ├── lookerhealthvacuum
│   │   │   │   ├── lookerhealthvacuum_test.go
│   │   │   │   └── lookerhealthvacuum.go
│   │   │   ├── lookermakedashboard
│   │   │   │   ├── lookermakedashboard_test.go
│   │   │   │   └── lookermakedashboard.go
│   │   │   ├── lookermakelook
│   │   │   │   ├── lookermakelook_test.go
│   │   │   │   └── lookermakelook.go
│   │   │   ├── lookerquery
│   │   │   │   ├── lookerquery_test.go
│   │   │   │   └── lookerquery.go
│   │   │   ├── lookerquerysql
│   │   │   │   ├── lookerquerysql_test.go
│   │   │   │   └── lookerquerysql.go
│   │   │   ├── lookerqueryurl
│   │   │   │   ├── lookerqueryurl_test.go
│   │   │   │   └── lookerqueryurl.go
│   │   │   ├── lookerrundashboard
│   │   │   │   ├── lookerrundashboard_test.go
│   │   │   │   └── lookerrundashboard.go
│   │   │   ├── lookerrunlook
│   │   │   │   ├── lookerrunlook_test.go
│   │   │   │   └── lookerrunlook.go
│   │   │   └── lookerupdateprojectfile
│   │   │       ├── lookerupdateprojectfile_test.go
│   │   │       └── lookerupdateprojectfile.go
│   │   ├── mindsdb
│   │   │   ├── mindsdbexecutesql
│   │   │   │   ├── mindsdbexecutesql_test.go
│   │   │   │   └── mindsdbexecutesql.go
│   │   │   └── mindsdbsql
│   │   │       ├── mindsdbsql_test.go
│   │   │       └── mindsdbsql.go
│   │   ├── mongodb
│   │   │   ├── mongodbaggregate
│   │   │   │   ├── mongodbaggregate_test.go
│   │   │   │   └── mongodbaggregate.go
│   │   │   ├── mongodbdeletemany
│   │   │   │   ├── mongodbdeletemany_test.go
│   │   │   │   └── mongodbdeletemany.go
│   │   │   ├── mongodbdeleteone
│   │   │   │   ├── mongodbdeleteone_test.go
│   │   │   │   └── mongodbdeleteone.go
│   │   │   ├── mongodbfind
│   │   │   │   ├── mongodbfind_test.go
│   │   │   │   └── mongodbfind.go
│   │   │   ├── mongodbfindone
│   │   │   │   ├── mongodbfindone_test.go
│   │   │   │   └── mongodbfindone.go
│   │   │   ├── mongodbinsertmany
│   │   │   │   ├── mongodbinsertmany_test.go
│   │   │   │   └── mongodbinsertmany.go
│   │   │   ├── mongodbinsertone
│   │   │   │   ├── mongodbinsertone_test.go
│   │   │   │   └── mongodbinsertone.go
│   │   │   ├── mongodbupdatemany
│   │   │   │   ├── mongodbupdatemany_test.go
│   │   │   │   └── mongodbupdatemany.go
│   │   │   └── mongodbupdateone
│   │   │       ├── mongodbupdateone_test.go
│   │   │       └── mongodbupdateone.go
│   │   ├── mssql
│   │   │   ├── mssqlexecutesql
│   │   │   │   ├── mssqlexecutesql_test.go
│   │   │   │   └── mssqlexecutesql.go
│   │   │   ├── mssqllisttables
│   │   │   │   ├── mssqllisttables_test.go
│   │   │   │   └── mssqllisttables.go
│   │   │   └── mssqlsql
│   │   │       ├── mssqlsql_test.go
│   │   │       └── mssqlsql.go
│   │   ├── mysql
│   │   │   ├── mysqlcommon
│   │   │   │   └── mysqlcommon.go
│   │   │   ├── mysqlexecutesql
│   │   │   │   ├── mysqlexecutesql_test.go
│   │   │   │   └── mysqlexecutesql.go
│   │   │   ├── mysqllistactivequeries
│   │   │   │   ├── mysqllistactivequeries_test.go
│   │   │   │   └── mysqllistactivequeries.go
│   │   │   ├── mysqllisttablefragmentation
│   │   │   │   ├── mysqllisttablefragmentation_test.go
│   │   │   │   └── mysqllisttablefragmentation.go
│   │   │   ├── mysqllisttables
│   │   │   │   ├── mysqllisttables_test.go
│   │   │   │   └── mysqllisttables.go
│   │   │   ├── mysqllisttablesmissinguniqueindexes
│   │   │   │   ├── mysqllisttablesmissinguniqueindexes_test.go
│   │   │   │   └── mysqllisttablesmissinguniqueindexes.go
│   │   │   └── mysqlsql
│   │   │       ├── mysqlsql_test.go
│   │   │       └── mysqlsql.go
│   │   ├── neo4j
│   │   │   ├── neo4jcypher
│   │   │   │   ├── neo4jcypher_test.go
│   │   │   │   └── neo4jcypher.go
│   │   │   ├── neo4jexecutecypher
│   │   │   │   ├── classifier
│   │   │   │   │   ├── classifier_test.go
│   │   │   │   │   └── classifier.go
│   │   │   │   ├── neo4jexecutecypher_test.go
│   │   │   │   └── neo4jexecutecypher.go
│   │   │   └── neo4jschema
│   │   │       ├── cache
│   │   │       │   ├── cache_test.go
│   │   │       │   └── cache.go
│   │   │       ├── helpers
│   │   │       │   ├── helpers_test.go
│   │   │       │   └── helpers.go
│   │   │       ├── neo4jschema_test.go
│   │   │       ├── neo4jschema.go
│   │   │       └── types
│   │   │           └── types.go
│   │   ├── oceanbase
│   │   │   ├── oceanbaseexecutesql
│   │   │   │   ├── oceanbaseexecutesql_test.go
│   │   │   │   └── oceanbaseexecutesql.go
│   │   │   └── oceanbasesql
│   │   │       ├── oceanbasesql_test.go
│   │   │       └── oceanbasesql.go
│   │   ├── oracle
│   │   │   ├── oracleexecutesql
│   │   │   │   └── oracleexecutesql.go
│   │   │   └── oraclesql
│   │   │       └── oraclesql.go
│   │   ├── parameters_test.go
│   │   ├── parameters.go
│   │   ├── postgres
│   │   │   ├── postgresexecutesql
│   │   │   │   ├── postgresexecutesql_test.go
│   │   │   │   └── postgresexecutesql.go
│   │   │   ├── postgreslistactivequeries
│   │   │   │   ├── postgreslistactivequeries_test.go
│   │   │   │   └── postgreslistactivequeries.go
│   │   │   ├── postgreslistavailableextensions
│   │   │   │   ├── postgreslistavailableextensions_test.go
│   │   │   │   └── postgreslistavailableextensions.go
│   │   │   ├── postgreslistinstalledextensions
│   │   │   │   ├── postgreslistinstalledextensions_test.go
│   │   │   │   └── postgreslistinstalledextensions.go
│   │   │   ├── postgreslistschemas
│   │   │   │   ├── postgreslistschemas_test.go
│   │   │   │   └── postgreslistschemas.go
│   │   │   ├── postgreslisttables
│   │   │   │   ├── postgreslisttables_test.go
│   │   │   │   └── postgreslisttables.go
│   │   │   ├── postgreslistviews
│   │   │   │   ├── postgreslistviews_test.go
│   │   │   │   └── postgreslistviews.go
│   │   │   └── postgressql
│   │   │       ├── postgressql_test.go
│   │   │       └── postgressql.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── serverlessspark
│   │   │   ├── serverlesssparkcancelbatch
│   │   │   │   ├── serverlesssparkcancelbatch_test.go
│   │   │   │   └── serverlesssparkcancelbatch.go
│   │   │   ├── serverlesssparkgetbatch
│   │   │   │   ├── serverlesssparkgetbatch_test.go
│   │   │   │   └── serverlesssparkgetbatch.go
│   │   │   └── serverlesssparklistbatches
│   │   │       ├── serverlesssparklistbatches_test.go
│   │   │       └── serverlesssparklistbatches.go
│   │   ├── singlestore
│   │   │   ├── singlestoreexecutesql
│   │   │   │   ├── singlestoreexecutesql_test.go
│   │   │   │   └── singlestoreexecutesql.go
│   │   │   └── singlestoresql
│   │   │       ├── singlestoresql_test.go
│   │   │       └── singlestoresql.go
│   │   ├── spanner
│   │   │   ├── spannerexecutesql
│   │   │   │   ├── spannerexecutesql_test.go
│   │   │   │   └── spannerexecutesql.go
│   │   │   ├── spannerlisttables
│   │   │   │   ├── spannerlisttables_test.go
│   │   │   │   └── spannerlisttables.go
│   │   │   └── spannersql
│   │   │       ├── spanner_test.go
│   │   │       └── spannersql.go
│   │   ├── sqlite
│   │   │   ├── sqliteexecutesql
│   │   │   │   ├── sqliteexecutesql_test.go
│   │   │   │   └── sqliteexecutesql.go
│   │   │   └── sqlitesql
│   │   │       ├── sqlitesql_test.go
│   │   │       └── sqlitesql.go
│   │   ├── tidb
│   │   │   ├── tidbexecutesql
│   │   │   │   ├── tidbexecutesql_test.go
│   │   │   │   └── tidbexecutesql.go
│   │   │   └── tidbsql
│   │   │       ├── tidbsql_test.go
│   │   │       └── tidbsql.go
│   │   ├── tools_test.go
│   │   ├── tools.go
│   │   ├── toolsets.go
│   │   ├── trino
│   │   │   ├── trinoexecutesql
│   │   │   │   ├── trinoexecutesql_test.go
│   │   │   │   └── trinoexecutesql.go
│   │   │   └── trinosql
│   │   │       ├── trinosql_test.go
│   │   │       └── trinosql.go
│   │   ├── utility
│   │   │   └── wait
│   │   │       ├── wait_test.go
│   │   │       └── wait.go
│   │   ├── valkey
│   │   │   ├── valkey_test.go
│   │   │   └── valkey.go
│   │   └── yugabytedbsql
│   │       ├── yugabytedbsql_test.go
│   │       └── yugabytedbsql.go
│   └── util
│       ├── orderedmap
│       │   ├── orderedmap_test.go
│       │   └── orderedmap.go
│       └── util.go
├── LICENSE
├── logo.png
├── main.go
├── MCP-TOOLBOX-EXTENSION.md
├── README.md
└── tests
    ├── alloydb
    │   ├── alloydb_integration_test.go
    │   └── alloydb_wait_for_operation_test.go
    ├── alloydbainl
    │   └── alloydb_ai_nl_integration_test.go
    ├── alloydbpg
    │   └── alloydb_pg_integration_test.go
    ├── auth.go
    ├── bigquery
    │   └── bigquery_integration_test.go
    ├── bigtable
    │   └── bigtable_integration_test.go
    ├── cassandra
    │   └── cassandra_integration_test.go
    ├── clickhouse
    │   └── clickhouse_integration_test.go
    ├── cloudhealthcare
    │   └── cloud_healthcare_integration_test.go
    ├── cloudmonitoring
    │   └── cloud_monitoring_integration_test.go
    ├── cloudsql
    │   ├── cloud_sql_create_database_test.go
    │   ├── cloud_sql_create_users_test.go
    │   ├── cloud_sql_get_instances_test.go
    │   ├── cloud_sql_list_databases_test.go
    │   ├── cloudsql_list_instances_test.go
    │   └── cloudsql_wait_for_operation_test.go
    ├── cloudsqlmssql
    │   ├── cloud_sql_mssql_create_instance_integration_test.go
    │   └── cloud_sql_mssql_integration_test.go
    ├── cloudsqlmysql
    │   ├── cloud_sql_mysql_create_instance_integration_test.go
    │   └── cloud_sql_mysql_integration_test.go
    ├── cloudsqlpg
    │   ├── cloud_sql_pg_create_instances_test.go
    │   └── cloud_sql_pg_integration_test.go
    ├── common.go
    ├── couchbase
    │   └── couchbase_integration_test.go
    ├── dataform
    │   └── dataform_integration_test.go
    ├── dataplex
    │   └── dataplex_integration_test.go
    ├── dgraph
    │   └── dgraph_integration_test.go
    ├── elasticsearch
    │   └── elasticsearch_integration_test.go
    ├── firebird
    │   └── firebird_integration_test.go
    ├── firestore
    │   └── firestore_integration_test.go
    ├── http
    │   └── http_integration_test.go
    ├── looker
    │   └── looker_integration_test.go
    ├── mindsdb
    │   └── mindsdb_integration_test.go
    ├── mongodb
    │   └── mongodb_integration_test.go
    ├── mssql
    │   └── mssql_integration_test.go
    ├── mysql
    │   └── mysql_integration_test.go
    ├── neo4j
    │   └── neo4j_integration_test.go
    ├── oceanbase
    │   └── oceanbase_integration_test.go
    ├── option.go
    ├── oracle
    │   └── oracle_integration_test.go
    ├── postgres
    │   └── postgres_integration_test.go
    ├── redis
    │   └── redis_test.go
    ├── server.go
    ├── serverlessspark
    │   └── serverless_spark_integration_test.go
    ├── singlestore
    │   └── singlestore_integration_test.go
    ├── source.go
    ├── spanner
    │   └── spanner_integration_test.go
    ├── sqlite
    │   └── sqlite_integration_test.go
    ├── tidb
    │   └── tidb_integration_test.go
    ├── tool.go
    ├── trino
    │   └── trino_integration_test.go
    ├── utility
    │   └── wait_integration_test.go
    ├── valkey
    │   └── valkey_test.go
    └── yugabytedb
        └── yugabytedb_integration_test.go
```

# Files

--------------------------------------------------------------------------------
/tests/cloudhealthcare/cloud_healthcare_integration_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// To run these tests, set the following environment variables:
// HEALTHCARE_PROJECT: Google Cloud project ID for healthcare resources.
// HEALTHCARE_REGION: Google Cloud region for healthcare resources.

package cloudhealthcare

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/googleapis/genai-toolbox/internal/sources"
	"github.com/googleapis/genai-toolbox/internal/testutils"
	"github.com/googleapis/genai-toolbox/tests"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/healthcare/v1"
	"google.golang.org/api/option"
)

var (
	healthcareSourceKind                  = "cloud-healthcare"
	getDatasetToolKind                    = "cloud-healthcare-get-dataset"
	listFHIRStoresToolKind                = "cloud-healthcare-list-fhir-stores"
	listDICOMStoresToolKind               = "cloud-healthcare-list-dicom-stores"
	getFHIRStoreToolKind                  = "cloud-healthcare-get-fhir-store"
	getFHIRStoreMetricsToolKind           = "cloud-healthcare-get-fhir-store-metrics"
	getFHIRResourceToolKind               = "cloud-healthcare-get-fhir-resource"
	fhirPatientSearchToolKind             = "cloud-healthcare-fhir-patient-search"
	fhirPatientEverythingToolKind         = "cloud-healthcare-fhir-patient-everything"
	fhirFetchPageToolKind                 = "cloud-healthcare-fhir-fetch-page"
	getDICOMStoreToolKind                 = "cloud-healthcare-get-dicom-store"
	getDICOMStoreMetricsToolKind          = "cloud-healthcare-get-dicom-store-metrics"
	searchDICOMStudiesToolKind            = "cloud-healthcare-search-dicom-studies"
	searchDICOMSeriesToolKind             = "cloud-healthcare-search-dicom-series"
	searchDICOMInstancesToolKind          = "cloud-healthcare-search-dicom-instances"
	retrieveRenderedDICOMInstanceToolKind = "cloud-healthcare-retrieve-rendered-dicom-instance"
	healthcareProject                     = os.Getenv("HEALTHCARE_PROJECT")
	healthcareRegion                      = os.Getenv("HEALTHCARE_REGION")
	healthcareDataset                     = os.Getenv("HEALTHCARE_DATASET")
	healthcarePrepopulatedDICOMStore      = os.Getenv("HEALTHCARE_PREPOPULATED_DICOM_STORE")
)

type DICOMInstance struct {
	study, series, instance string
}

var (
	singleFrameDICOMInstance = DICOMInstance{
		study:    "1.2.840.113619.2.176.3596.3364818.7819.1259708454.105",
		series:   "1.2.840.113619.2.176.3596.3364818.7819.1259708454.108",
		instance: "1.2.840.113619.2.176.3596.3364818.7271.1259708501.876",
	}
	multiFrameDICOMInstance = DICOMInstance{
		study:    "1.2.826.0.1.3680043.9.5704.649259287",
		series:   "1.2.826.0.1.3680043.9.5704.983743739",
		instance: "1.2.826.0.1.3680043.9.5704.983743739.2",
	}
)

func getHealthcareVars(t *testing.T) map[string]any {
	switch "" {
	case healthcareProject:
		t.Fatal("'HEALTHCARE_PROJECT' not set")
	case healthcareRegion:
		t.Fatal("'HEALTHCARE_REGION' not set")
	case healthcareDataset:
		t.Fatal("'HEALTHCARE_DATASET' not set")
	case healthcarePrepopulatedDICOMStore:
		t.Fatal("'HEALTHCARE_PREPOPULATED_DICOM_STORE' not set")
	}
	return map[string]any{
		"kind":    healthcareSourceKind,
		"project": healthcareProject,
		"region":  healthcareRegion,
		"dataset": healthcareDataset,
	}
}

func TestHealthcareToolEndpoints(t *testing.T) {
	sourceConfig := getHealthcareVars(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	defer cancel()

	healthcareService, err := newHealthcareService(ctx)
	if err != nil {
		t.Fatalf("failed to create healthcare service: %v", err)
	}

	fhirStoreID := "fhir-store-" + uuid.New().String()
	dicomStoreID := "dicom-store-" + uuid.New().String()

	patient1ID, patient2ID, teardown := setupHealthcareResources(t, healthcareService, healthcareDataset, fhirStoreID, dicomStoreID)
	defer teardown(t)

	toolsFile := getToolsConfig(sourceConfig)
	toolsFile = addClientAuthSourceConfig(t, toolsFile)

	var args []string
	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
	if err != nil {
		t.Fatalf("command initialization returned an error: %s", err)
	}
	defer cleanup()

	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
	if err != nil {
		t.Logf("toolbox command logs: %s", out)
		t.Fatalf("toolbox didn't start successfully: %s", err)
	}

	datasetWant := fmt.Sprintf(`"name":"projects/%s/locations/%s/datasets/%s"`, healthcareProject, healthcareRegion, healthcareDataset)
	fhirStoreWant := fmt.Sprintf(`"name":"projects/%s/locations/%s/datasets/%s/fhirStores/%s"`, healthcareProject, healthcareRegion, healthcareDataset, fhirStoreID)
	dicomStoreWant := fmt.Sprintf(`"name":"projects/%s/locations/%s/datasets/%s/dicomStores/%s"`, healthcareProject, healthcareRegion, healthcareDataset, dicomStoreID)

	runGetDatasetToolInvokeTest(t, datasetWant)
	runListFHIRStoresToolInvokeTest(t, fhirStoreWant)
	runListDICOMStoresToolInvokeTest(t, dicomStoreWant)
	runGetFHIRStoreToolInvokeTest(t, fhirStoreID, fhirStoreWant)
	runGetFHIRStoreMetricsToolInvokeTest(t, fhirStoreID, `"metrics"`)
	runGetFHIRResourceToolInvokeTest(t, fhirStoreID, "Patient", patient1ID, `"id":"`+patient1ID+`"`)
	runFHIRPatientSearchToolInvokeTest(t, fhirStoreID, patient1ID, patient2ID)
	runFHIRPatientEverythingToolInvokeTest(t, fhirStoreID, patient1ID, `"resourceType":"Bundle"`)

	nextURL := getNextPageURLForPatientEverything(t, fhirStoreID, patient2ID)
	runFHIRFetchPageToolInvokeTest(t, nextURL, `"total":1`)

	runGetDICOMStoreToolInvokeTest(t, dicomStoreID, dicomStoreWant)
	runGetDICOMStoreMetricsToolInvokeTest(t, healthcarePrepopulatedDICOMStore, `"structuredStorageSizeBytes"`)
	runSearchDICOMStudiesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
	runSearchDICOMSeriesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
	runSearchDICOMInstancesToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
	runRetrieveRenderedDICOMInstanceToolInvokeTest(t, healthcarePrepopulatedDICOMStore)
}

func TestHealthcareToolWithStoreRestriction(t *testing.T) {
	sourceConfig := getHealthcareVars(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	defer cancel()

	healthcareService, err := newHealthcareService(ctx)
	if err != nil {
		t.Fatalf("failed to create healthcare service: %v", err)
	}

	// Create stores
	allowedFHIRStoreID := "fhir-store-allowed-" + uuid.New().String()
	allowedDICOMStoreID := "dicom-store-allowed-" + uuid.New().String()
	disallowedFHIRStoreID := "fhir-store-disallowed-" + uuid.New().String()
	disallowedDICOMStoreID := "dicom-store-disallowed-" + uuid.New().String()

	_, _, teardownAllowedStores := setupHealthcareResources(t, healthcareService, healthcareDataset, allowedFHIRStoreID, allowedDICOMStoreID)
	defer teardownAllowedStores(t)
	_, _, teardownDisallowedStores := setupHealthcareResources(t, healthcareService, healthcareDataset, disallowedFHIRStoreID, disallowedDICOMStoreID)
	defer teardownDisallowedStores(t)

	// Configure source with dataset restriction.
	sourceConfig["allowedFhirStores"] = []string{allowedFHIRStoreID}
	sourceConfig["allowedDicomStores"] = []string{allowedDICOMStoreID}

	// Configure tool
	toolsConfig := map[string]any{
		"list-fhir-stores-restricted": map[string]any{
			"kind":        "cloud-healthcare-list-fhir-stores",
			"source":      "my-instance",
			"description": "Tool to list fhir stores",
		},
		"list-dicom-stores-restricted": map[string]any{
			"kind":        "cloud-healthcare-list-dicom-stores",
			"source":      "my-instance",
			"description": "Tool to list dicom stores",
		},
	}

	// Create config file
	config := map[string]any{
		"sources": map[string]any{
			"my-instance": sourceConfig,
		},
		"tools": toolsConfig,
	}

	// Start server
	cmd, cleanup, err := tests.StartCmd(ctx, config)
	if err != nil {
		t.Fatalf("command initialization returned an error: %s", err)
	}
	defer cleanup()

	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
	if err != nil {
		t.Logf("toolbox command logs: \n%s", out)
		t.Fatalf("toolbox didn't start successfully: %s", err)
	}

	// Run tests
	runListFHIRStoresWithRestriction(t, allowedFHIRStoreID, disallowedFHIRStoreID)
	runListDICOMStoresWithRestriction(t, allowedDICOMStoreID, disallowedDICOMStoreID)
}

func createFHIRResource(t *testing.T, service *healthcare.Service, fhirStoreName, resourceType string, resourceBody io.Reader) (string, string) {
	resp, err := service.Projects.Locations.Datasets.FhirStores.Fhir.Create(fhirStoreName, resourceType, resourceBody).Do()
	if err != nil {
		t.Fatalf("failed to create FHIR resource: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		t.Fatalf("failed to create FHIR resource, status: %d, body: %s", resp.StatusCode, string(body))
	}

	var result map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}
	id := result["id"].(string)
	return id, fmt.Sprintf("%s/%s", resourceType, id)
}

func newHealthcareService(ctx context.Context) (*healthcare.Service, error) {
	creds, err := google.FindDefaultCredentials(ctx, healthcare.CloudHealthcareScope)
	if err != nil {
		return nil, fmt.Errorf("failed to find default credentials: %w", err)
	}

	healthcareService, err := healthcare.NewService(ctx, option.WithCredentials(creds))
	if err != nil {
		return nil, fmt.Errorf("failed to create healthcare service: %w", err)
	}
	return healthcareService, nil
}

func setupHealthcareResources(t *testing.T, service *healthcare.Service, datasetID, fhirStoreID, dicomStoreID string) (string, string, func(*testing.T)) {
	datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", healthcareProject, healthcareRegion, datasetID)
	var err error

	// Create FHIR store
	fhirStore := &healthcare.FhirStore{Version: "R4"}
	if fhirStore, err = service.Projects.Locations.Datasets.FhirStores.Create(datasetName, fhirStore).FhirStoreId(fhirStoreID).Do(); err != nil {
		t.Fatalf("failed to create fhir store: %v", err)
	}

	// Create DICOM store
	dicomStore := &healthcare.DicomStore{}
	if dicomStore, err = service.Projects.Locations.Datasets.DicomStores.Create(datasetName, dicomStore).DicomStoreId(dicomStoreID).Do(); err != nil {
		t.Fatalf("failed to create dicom store: %v", err)
	}

	// Create Patient 1
	patient1Body := bytes.NewBuffer([]byte(`{
		"resourceType":"Patient",
		"name":[{"use":"official","family":"Smith","given":["John"]}],
		"birthDate":"1980-01-01",
		"gender":"male",
		"address":[{"use":"home","line":["123 Main St"],"city":"san fransisco","state":"CA","postalCode":"12345","country":"USA"}],
		"active":true,
		"deceasedBoolean":false,
		"telecom":[{"system":"phone","value":"555-1234","use":"home"},{"system":"email","value":"[email protected]","use":"work"}],
		"gender":"male",
		"identifier":[{"system":"http://hospital.org","value":"1234567"}],
		"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"en","display":"English"}]},"preferred":true}]
	}`))
	patient1ID, patient1Name := createFHIRResource(t, service, fhirStore.Name, "Patient", patient1Body)

	// Create Observation for Patient 1
	observation1Body := bytes.NewBuffer([]byte(fmt.Sprintf(`
	{
		"resourceType": "Observation",
		"status": "final",
		"code": { "coding": [{ "system": "http://loinc.org", "code": "29463-7", "display": "Body Weight" }] },
		"subject": { "reference": "%s" },
		"valueQuantity": { "value": 185, "unit": "lbs", "system": "http://unitsofmeasure.org", "code": "[lb_av]" }
	}`, patient1Name)))
	createFHIRResource(t, service, fhirStore.Name, "Observation", observation1Body)

	// Create Patient 2
	patient2Body := bytes.NewBuffer([]byte(`{"resourceType":"Patient","name":[{"use":"official","family":"Doe","given":["Jane"]}]}`))
	patient2ID, patient2Name := createFHIRResource(t, service, fhirStore.Name, "Patient", patient2Body)

	// Create 101 Observations for Patient 2
	for i := 0; i < 101; i++ {
		observation2Body := bytes.NewBuffer([]byte(fmt.Sprintf(`
		{
			"resourceType": "Observation",
			"status": "final",
			"code": { "coding": [{ "system": "http://loinc.org", "code": "8302-2", "display": "Body Height" }] },
			"subject": { "reference": "%s" },
			"valueQuantity": { "value": 68, "unit": "in", "system": "http://unitsofmeasure.org", "code": "[in_i]" }
		}`, patient2Name)))
		createFHIRResource(t, service, fhirStore.Name, "Observation", observation2Body)
	}

	teardown := func(t *testing.T) {
		if _, err := service.Projects.Locations.Datasets.FhirStores.Delete(fhirStore.Name).Do(); err != nil {
			t.Logf("failed to delete fhir store: %v", err)
		}
		if _, err := service.Projects.Locations.Datasets.DicomStores.Delete(dicomStore.Name).Do(); err != nil {
			t.Logf("failed to delete dicom store: %v", err)
		}
	}
	return patient1ID, patient2ID, teardown
}

func getToolsConfig(sourceConfig map[string]any) map[string]any {
	config := map[string]any{
		"sources": map[string]any{
			"my-instance": sourceConfig,
		},
		"tools": map[string]any{
			"my-get-dataset-tool": map[string]any{
				"kind":        getDatasetToolKind,
				"source":      "my-instance",
				"description": "Tool to get a healthcare dataset",
			},
			"my-list-fhir-stores-tool": map[string]any{
				"kind":        listFHIRStoresToolKind,
				"source":      "my-instance",
				"description": "Tool to list FHIR stores",
			},
			"my-list-dicom-stores-tool": map[string]any{
				"kind":        listDICOMStoresToolKind,
				"source":      "my-instance",
				"description": "Tool to list DICOM stores",
			},
			"my-get-fhir-store-tool": map[string]any{
				"kind":        getFHIRStoreToolKind,
				"source":      "my-instance",
				"description": "Tool to get a FHIR store",
			},
			"my-get-fhir-store-metrics-tool": map[string]any{
				"kind":        getFHIRStoreMetricsToolKind,
				"source":      "my-instance",
				"description": "Tool to get FHIR store metrics",
			},
			"my-get-fhir-resource-tool": map[string]any{
				"kind":        getFHIRResourceToolKind,
				"source":      "my-instance",
				"description": "Tool to get FHIR resource",
			},
			"my-fhir-patient-search-tool": map[string]any{
				"kind":        fhirPatientSearchToolKind,
				"source":      "my-instance",
				"description": "Tool to search for patients",
			},
			"my-fhir-patient-everything-tool": map[string]any{
				"kind":        fhirPatientEverythingToolKind,
				"source":      "my-instance",
				"description": "Tool for patient everything",
			},
			"my-fhir-fetch-page-tool": map[string]any{
				"kind":        fhirFetchPageToolKind,
				"source":      "my-instance",
				"description": "Tool to fetch a page of FHIR resources",
			},
			"my-get-dicom-store-tool": map[string]any{
				"kind":        getDICOMStoreToolKind,
				"source":      "my-instance",
				"description": "Tool to get a DICOM store",
			},
			"my-get-dicom-store-metrics-tool": map[string]any{
				"kind":        getDICOMStoreMetricsToolKind,
				"source":      "my-instance",
				"description": "Tool to get DICOM store metrics",
			},
			"my-search-dicom-studies-tool": map[string]any{
				"kind":        searchDICOMStudiesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM studies",
			},
			"my-search-dicom-series-tool": map[string]any{
				"kind":        searchDICOMSeriesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM series",
			},
			"my-search-dicom-instances-tool": map[string]any{
				"kind":        searchDICOMInstancesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM instances",
			},
			"my-retrieve-rendered-dicom-instance-tool": map[string]any{
				"kind":        retrieveRenderedDICOMInstanceToolKind,
				"source":      "my-instance",
				"description": "Tool to retrieve rendered DICOM instance",
			},
			"my-client-auth-get-dataset-tool": map[string]any{
				"kind":        getDatasetToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get a healthcare dataset",
			},
			"my-client-auth-list-fhir-stores-tool": map[string]any{
				"kind":        listFHIRStoresToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to list FHIR stores",
			},
			"my-client-auth-list-dicom-stores-tool": map[string]any{
				"kind":        listDICOMStoresToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to list DICOM stores",
			},
			"my-client-auth-get-fhir-store-tool": map[string]any{
				"kind":        getFHIRStoreToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get a FHIR store",
			},
			"my-client-auth-get-fhir-store-metrics-tool": map[string]any{
				"kind":        getFHIRStoreMetricsToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get FHIR store metrics",
			},
			"my-client-auth-get-fhir-resource-tool": map[string]any{
				"kind":        getFHIRResourceToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get FHIR resource",
			},
			"my-client-auth-fhir-patient-search-tool": map[string]any{
				"kind":        fhirPatientSearchToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to search for patients",
			},
			"my-client-auth-fhir-patient-everything-tool": map[string]any{
				"kind":        fhirPatientEverythingToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool for patient everything",
			},
			"my-client-auth-fhir-fetch-page-tool": map[string]any{
				"kind":        fhirFetchPageToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to fetch a page of FHIR resources",
			},
			"my-client-auth-get-dicom-store-tool": map[string]any{
				"kind":        getDICOMStoreToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get a DICOM store",
			},
			"my-client-auth-get-dicom-store-metrics-tool": map[string]any{
				"kind":        getDICOMStoreMetricsToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to get DICOM store metrics",
			},
			"my-client-auth-search-dicom-studies-tool": map[string]any{
				"kind":        searchDICOMStudiesToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to search DICOM studies",
			},
			"my-client-auth-search-dicom-series-tool": map[string]any{
				"kind":        searchDICOMSeriesToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to search DICOM series",
			},
			"my-client-auth-search-dicom-instances-tool": map[string]any{
				"kind":        searchDICOMInstancesToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to search DICOM instances",
			},
			"my-client-auth-retrieve-rendered-dicom-instance-tool": map[string]any{
				"kind":        retrieveRenderedDICOMInstanceToolKind,
				"source":      "my-client-auth-source",
				"description": "Tool to retrieve rendered DICOM instance",
			},
			"my-auth-get-dataset-tool": map[string]any{
				"kind":        getDatasetToolKind,
				"source":      "my-instance",
				"description": "Tool to get a healthcare dataset with auth",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-list-fhir-stores-tool": map[string]any{
				"kind":        listFHIRStoresToolKind,
				"source":      "my-instance",
				"description": "Tool to list FHIR stores with auth",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-list-dicom-stores-tool": map[string]any{
				"kind":        listDICOMStoresToolKind,
				"source":      "my-instance",
				"description": "Tool to list DICOM stores with auth",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-get-fhir-store-tool": map[string]any{
				"kind":        getFHIRStoreToolKind,
				"source":      "my-instance",
				"description": "Tool to get a FHIR store",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-get-fhir-store-metrics-tool": map[string]any{
				"kind":        getFHIRStoreMetricsToolKind,
				"source":      "my-instance",
				"description": "Tool to get FHIR store metrics",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-get-fhir-resource-tool": map[string]any{
				"kind":        getFHIRResourceToolKind,
				"source":      "my-instance",
				"description": "Tool to get FHIR resource",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-fhir-patient-search-tool": map[string]any{
				"kind":        fhirPatientSearchToolKind,
				"source":      "my-instance",
				"description": "Tool to search for patients",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-fhir-patient-everything-tool": map[string]any{
				"kind":        fhirPatientEverythingToolKind,
				"source":      "my-instance",
				"description": "Tool for patient everything",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-fhir-fetch-page-tool": map[string]any{
				"kind":        fhirFetchPageToolKind,
				"source":      "my-instance",
				"description": "Tool to fetch a page of FHIR resources",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-get-dicom-store-tool": map[string]any{
				"kind":        getDICOMStoreToolKind,
				"source":      "my-instance",
				"description": "Tool to get a DICOM store",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-get-dicom-store-metrics-tool": map[string]any{
				"kind":        getDICOMStoreMetricsToolKind,
				"source":      "my-instance",
				"description": "Tool to get DICOM store metrics",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-search-dicom-studies-tool": map[string]any{
				"kind":        searchDICOMStudiesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM studies",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-search-dicom-series-tool": map[string]any{
				"kind":        searchDICOMSeriesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM series",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-search-dicom-instances-tool": map[string]any{
				"kind":        searchDICOMInstancesToolKind,
				"source":      "my-instance",
				"description": "Tool to search DICOM instances",
				"authRequired": []string{
					"my-google-auth",
				},
			},
			"my-auth-retrieve-rendered-dicom-instance-tool": map[string]any{
				"kind":        retrieveRenderedDICOMInstanceToolKind,
				"source":      "my-instance",
				"description": "Tool to retrieve rendered DICOM instance",
				"authRequired": []string{
					"my-google-auth",
				},
			},
		},
		"authServices": map[string]any{
			"my-google-auth": map[string]any{
				"kind":     "google",
				"clientId": tests.ClientId,
			},
		},
	}
	return config
}

func addClientAuthSourceConfig(t *testing.T, config map[string]any) map[string]any {
	sources, ok := config["sources"].(map[string]any)
	if !ok {
		t.Fatalf("unable to get sources from config")
	}
	sources["my-client-auth-source"] = map[string]any{
		"kind":           healthcareSourceKind,
		"project":        healthcareProject,
		"region":         healthcareRegion,
		"dataset":        healthcareDataset,
		"useClientOAuth": true,
	}
	config["sources"] = sources
	return config
}

func runGetDatasetToolInvokeTest(t *testing.T, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-dataset-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dataset-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dataset-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dataset-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dataset-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dataset-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-dataset-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dataset-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-dataset-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dataset-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dataset-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runListFHIRStoresToolInvokeTest(t *testing.T, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-list-fhir-stores-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-fhir-stores-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-fhir-stores-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-fhir-stores-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-list-fhir-stores-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-list-fhir-stores-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-list-fhir-stores-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-list-fhir-stores-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-fhir-stores-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runListDICOMStoresToolInvokeTest(t *testing.T, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-list-dicom-stores-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-dicom-stores-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-dicom-stores-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-list-dicom-stores-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-list-dicom-stores-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-list-dicom-stores-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-list-dicom-stores-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-list-dicom-stores-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-list-dicom-stores-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runGetFHIRStoreToolInvokeTest(t *testing.T, fhirStoreID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-fhir-store-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-fhir-store-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-fhir-store-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runGetFHIRStoreMetricsToolInvokeTest(t *testing.T, fhirStoreID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-fhir-store-metrics-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-metrics-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-metrics-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-store-metrics-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-fhir-store-metrics-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-fhir-store-metrics-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-metrics-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-metrics-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-store-metrics-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-store-metrics-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runGetFHIRResourceToolInvokeTest(t *testing.T, storeID, resType, resID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-fhir-resource-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-resource-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-resource-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-fhir-resource-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-fhir-resource-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-fhir-resource-tool with non-existent resource",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"foo"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-fhir-resource-tool with missing required id parameter",
			api:           "http://127.0.0.1:5000/api/tool/my-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-resource-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-fhir-resource-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-fhir-resource-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-fhir-resource-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + storeID + `", "resourceType":"` + resType + `", "resourceID":"` + resID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runFHIRPatientSearchToolInvokeTest(t *testing.T, fhirStoreID string, patientIDs ...string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken
	want := `"total":` + fmt.Sprintf(`%d`, len(patientIDs))

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-fhir-patient-search-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-search-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-search-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-search-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-fhir-patient-search-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-fhir-patient-search-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-patient-search-tool wrong parameter type",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "name":true}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-patient-search-tool filters",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "name":"john","gender":"male","state":"CA","active":"true","birthDateRange":"1970-01-01/2000-12-31"}`)),
			want:          patientIDs[0],
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-search-tool filters 2",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "givenName":"john","addressSubstring":"main st","email":"[email protected]","phone":"555-1234","language":"en","deceased":"false"}`)),
			want:          patientIDs[0],
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-search-tool filters 3",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "familyName":"smith","country":"USA","addressUse":"home","postalCode":"12345","identifier":"1234567"}`)),
			want:          patientIDs[0],
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-search-tool zero matches",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "gender":"unknown"}`)),
			want:          `"total":0`,
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-search-tool match second patient only",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `","familyName":"Doe"}`)),
			want:          patientIDs[1],
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-search-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-search-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-search-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-search-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runFHIRPatientEverythingToolInvokeTest(t *testing.T, fhirStoreID, patientID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-fhir-patient-everything-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-everything-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-everything-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-patient-everything-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-fhir-patient-everything-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-patient-everything-tool with non-existent patient",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"foo"`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-patient-everything-tool with invalid since filter format",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `","sinceFilter":"October 10th, 2023"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-patient-everything-tool with type and since filters",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `","sinceFilter":"2023-01-01T00:00:00Z","resourceTypesFilter":["Observation"]}`)),
			want:          `"total":2`,
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-everything-tool with type and since keeps only patient",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `","sinceFilter":"` + time.Now().Format(time.RFC3339) + `","resourceTypesFilter":["Observation","Encounter"]}`)),
			want:          `"total":1`,
			isErr:         false,
		},
		{
			name:          "invoke my-fhir-patient-everything-tool with type keeps only patient",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `","resourceTypesFilter":["Encounter","Claim"]}`)),
			want:          `"total":1`,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-everything-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-everything-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-fhir-patient-everything-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-patient-everything-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + fhirStoreID + `", "patientID":"` + patientID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runFHIRFetchPageToolInvokeTest(t *testing.T, pageURL, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-fhir-fetch-page-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-fetch-page-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-fetch-page-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-fhir-fetch-page-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-fhir-fetch-page-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-fhir-fetch-page-tool with invalid url",
			api:           "http://127.0.0.1:5000/api/tool/my-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"google.com"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-fhir-fetch-page-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-fhir-fetch-page-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-fhir-fetch-page-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-fhir-fetch-page-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"pageURL":"` + pageURL + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func getNextPageURLForPatientEverything(t *testing.T, fhirStoreID, patientID string) string {
	api := "http://127.0.0.1:5000/api/tool/my-fhir-patient-everything-tool/invoke"
	reqBody := fmt.Sprintf(`{"storeID": "%s", "patientID": "%s"}`, fhirStoreID, patientID)
	resp, bodyBytes := tests.RunRequest(t, http.MethodPost, api, bytes.NewBuffer([]byte(reqBody)), map[string]string{"Content-type": "application/json"})
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
	}

	var body map[string]interface{}
	err := json.Unmarshal(bodyBytes, &body)
	if err != nil {
		t.Fatalf("error parsing response body")
	}

	resultStr, ok := body["result"].(string)
	if !ok {
		t.Fatalf("unable to find result in response body")
	}

	var resultJSON map[string]interface{}
	if err := json.Unmarshal([]byte(resultStr), &resultJSON); err != nil {
		t.Fatalf("failed to unmarshal result string: %v", err)
	}

	links, ok := resultJSON["link"].([]interface{})
	if !ok {
		t.Fatalf("no link field in result")
	}

	for _, l := range links {
		link := l.(map[string]interface{})
		if relation, ok := link["relation"].(string); ok && relation == "next" {
			if url, ok := link["url"].(string); ok {
				return url
			}
		}
	}
	t.Fatalf("next link not found in patient everything response")
	return ""
}

func runTest(t *testing.T, api string, requestHeader map[string]string, requestBody io.Reader) (string, int) {
	resp, bodyBytes := tests.RunRequest(t, http.MethodPost, api, requestBody, requestHeader)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", resp.StatusCode
	}

	var body map[string]interface{}
	err := json.Unmarshal(bodyBytes, &body)
	if err != nil {
		t.Fatalf("error parsing response body")
	}

	got, ok := body["result"].(string)
	if !ok {
		t.Fatalf("unable to find result in response body")
	}
	return got, http.StatusOK
}

func runListFHIRStoresWithRestriction(t *testing.T, allowedFHIRStore, disallowedFHIRStore string) {
	api := "http://127.0.0.1:5000/api/tool/list-fhir-stores-restricted/invoke"
	got, status := runTest(t, api, map[string]string{"Content-type": "application/json"}, bytes.NewBuffer([]byte(`{}`)))
	if status != http.StatusOK {
		t.Fatalf("expected status OK but got %d", status)
	}

	if !strings.Contains(got, allowedFHIRStore) {
		t.Fatalf("expected %q to contain %q, but it did not", got, allowedFHIRStore)
	}
	if strings.Contains(got, disallowedFHIRStore) {
		t.Fatalf("expected %q to NOT contain %q, but it did", got, disallowedFHIRStore)
	}
}

func runListDICOMStoresWithRestriction(t *testing.T, allowedDICOMStore, disallowedDICOMStore string) {
	api := "http://127.0.0.1:5000/api/tool/list-dicom-stores-restricted/invoke"
	got, status := runTest(t, api, map[string]string{"Content-type": "application/json"}, bytes.NewBuffer([]byte(`{}`)))
	if status != http.StatusOK {
		t.Fatalf("expected status OK but got %d", status)
	}

	if !strings.Contains(got, allowedDICOMStore) {
		t.Fatalf("expected %q to contain %q, but it did not", got, allowedDICOMStore)
	}
	if strings.Contains(got, disallowedDICOMStore) {
		t.Fatalf("expected %q to NOT contain %q, but it did", got, disallowedDICOMStore)
	}
}

func runGetDICOMStoreToolInvokeTest(t *testing.T, dicomStoreID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-dicom-store-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-dicom-store-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-dicom-store-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runGetDICOMStoreMetricsToolInvokeTest(t *testing.T, dicomStoreID, want string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-get-dicom-store-metrics-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-metrics-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-metrics-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-get-dicom-store-metrics-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-get-dicom-store-metrics-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-get-dicom-store-metrics-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-metrics-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          want,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-metrics-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-get-dicom-store-metrics-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-get-dicom-store-metrics-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runSearchDICOMStudiesToolInvokeTest(t *testing.T, dicomStoreID string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-search-dicom-studies-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.study,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-studies-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.study,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-studies-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.study,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-studies-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-search-dicom-studies-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-studies-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-studies-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.study,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-search-dicom-studies-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-studies-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-studies-tool with patient name and fuzzy matching",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "PatientName":"Andrew", "fuzzymatching":true}`)),
			want:          multiFrameDICOMInstance.study,
			isErr:         false,
		},
		{
			name:          "invoke my-search-dicom-studies-tool with patient id filter",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-studies-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "PatientID":"Joelle-del"}`)),
			want:          singleFrameDICOMInstance.study,
			isErr:         false,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runSearchDICOMSeriesToolInvokeTest(t *testing.T, dicomStoreID string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-search-dicom-series-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.series,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-series-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.series,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-series-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.series,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-series-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-search-dicom-series-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-series-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-series-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.series,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-search-dicom-series-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-series-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-series-tool with study date and referring physician name filters",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyDate":"20170101-20171231", "ReferringPhysicianName":"Frederick^Bryant^^Ph.D."}`)),
			want:          multiFrameDICOMInstance.series,
			isErr:         false,
		},
		{
			name:          "invoke my-search-dicom-series-tool with series instance uid",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-series-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "SeriesInstanceUID":"1.2.840.113619.2.176.3596.3364818.7819.1259708454.108"}`)),
			want:          singleFrameDICOMInstance.series,
			isErr:         false,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runSearchDICOMInstancesToolInvokeTest(t *testing.T, dicomStoreID string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-search-dicom-instances-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.instance,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-instances-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.instance,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-instances-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          multiFrameDICOMInstance.instance,
			isErr:         false,
		},
		{
			name:          "invoke my-auth-search-dicom-instances-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-search-dicom-instances-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-instances-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-instances-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			want:          singleFrameDICOMInstance.instance,
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-search-dicom-instances-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-search-dicom-instances-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-search-dicom-instances-tool with modality filter",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "modality":"SM"}`)),
			want:          multiFrameDICOMInstance.instance,
			isErr:         false,
		},
		{
			name:          "invoke my-search-dicom-instances-tool with include attribute",
			api:           "http://127.0.0.1:5000/api/tool/my-search-dicom-instances-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "includefield":["52009230"]}`)),
			want:          `"52009230"`,
			isErr:         false,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			got, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			} else if !strings.Contains(got, tc.want) {
				t.Errorf("expected result to contain %q but got %q", tc.want, got)
			}
		})
	}
}

func runRetrieveRenderedDICOMInstanceToolInvokeTest(t *testing.T, dicomStoreID string) {
	idToken, err := tests.GetGoogleIdToken(tests.ClientId)
	if err != nil {
		t.Fatalf("error getting Google ID token: %s", err)
	}

	accessToken, err := sources.GetIAMAccessToken(t.Context())
	if err != nil {
		t.Fatalf("error getting access token from ADC: %s", err)
	}
	accessToken = "Bearer " + accessToken

	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		isErr         bool
	}{
		{
			name:          "invoke my-retrieve-rendered-dicom-instance-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         false,
		},
		{
			name:          "invoke my-auth-retrieve-rendered-dicom-instance-tool with auth",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         false,
		},
		{
			name:          "invoke my-auth-retrieve-rendered-dicom-instance-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         false,
		},
		{
			name:          "invoke my-auth-retrieve-rendered-dicom-instance-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-auth-retrieve-rendered-dicom-instance-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{"Authorization": "invalid-token"},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-retrieve-rendered-dicom-instance-tool with invalid storeID",
			api:           "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"invalid-store", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-retrieve-rendered-dicom-instance-tool with client auth",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{"Authorization": accessToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         false,
		},
		{
			name:          "invoke my-client-auth-retrieve-rendered-dicom-instance-tool without auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-client-auth-retrieve-rendered-dicom-instance-tool with invalid auth token",
			api:           "http://127.0.0.1:5000/api/tool/my-client-auth-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{"my-google-auth_token": idToken},
			requestBody:   bytes.NewBuffer([]byte(`{"storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-retrieve-rendered-dicom-instance-tool second frame on single-frame instance",
			api:           "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"FrameNumber": 2, "storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + singleFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + singleFrameDICOMInstance.series + `", "SOPInstanceUID":"` + singleFrameDICOMInstance.instance + `"}`)),
			isErr:         true,
		},
		{
			name:          "invoke my-retrieve-rendered-dicom-instance-tool second frame on multi-frame instance",
			api:           "http://127.0.0.1:5000/api/tool/my-retrieve-rendered-dicom-instance-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{"FrameNumber": 2, "storeID":"` + dicomStoreID + `", "StudyInstanceUID":"` + multiFrameDICOMInstance.study + `", "SeriesInstanceUID":"` + multiFrameDICOMInstance.series + `", "SOPInstanceUID":"` + multiFrameDICOMInstance.instance + `"}`)),
			isErr:         false,
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			_, status := runTest(t, tc.api, tc.requestHeader, tc.requestBody)
			if tc.isErr {
				if status == http.StatusOK {
					t.Errorf("expected error but got success")
				}
				return
			}
			if status != http.StatusOK {
				t.Errorf("expected status OK but got %d", status)
			}
		})
	}
}

```
Page 43/44FirstPrevNextLast