This is page 53 of 59. Use http://codebase.md/googleapis/genai-toolbox?lines=true&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
--------------------------------------------------------------------------------
/cmd/root_test.go:
--------------------------------------------------------------------------------
```go
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cmd
16 |
17 | import (
18 | "bytes"
19 | "context"
20 | _ "embed"
21 | "fmt"
22 | "io"
23 | "os"
24 | "path"
25 | "path/filepath"
26 | "regexp"
27 | "runtime"
28 | "strings"
29 | "testing"
30 | "time"
31 |
32 | "github.com/google/go-cmp/cmp"
33 |
34 | "github.com/googleapis/genai-toolbox/internal/auth/google"
35 | "github.com/googleapis/genai-toolbox/internal/log"
36 | "github.com/googleapis/genai-toolbox/internal/prebuiltconfigs"
37 | "github.com/googleapis/genai-toolbox/internal/server"
38 | cloudsqlpgsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg"
39 | httpsrc "github.com/googleapis/genai-toolbox/internal/sources/http"
40 | "github.com/googleapis/genai-toolbox/internal/telemetry"
41 | "github.com/googleapis/genai-toolbox/internal/testutils"
42 | "github.com/googleapis/genai-toolbox/internal/tools"
43 | "github.com/googleapis/genai-toolbox/internal/tools/http"
44 | "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql"
45 | "github.com/googleapis/genai-toolbox/internal/util"
46 | "github.com/spf13/cobra"
47 | )
48 |
49 | func withDefaults(c server.ServerConfig) server.ServerConfig {
50 | data, _ := os.ReadFile("version.txt")
51 | version := strings.TrimSpace(string(data)) // Preserving 'data', new var for clarity
52 | c.Version = version + "+" + strings.Join([]string{"dev", runtime.GOOS, runtime.GOARCH}, ".")
53 |
54 | if c.Address == "" {
55 | c.Address = "127.0.0.1"
56 | }
57 | if c.Port == 0 {
58 | c.Port = 5000
59 | }
60 | if c.TelemetryServiceName == "" {
61 | c.TelemetryServiceName = "toolbox"
62 | }
63 | return c
64 | }
65 |
66 | func invokeCommand(args []string) (*Command, string, error) {
67 | c := NewCommand()
68 |
69 | // Keep the test output quiet
70 | c.SilenceUsage = true
71 | c.SilenceErrors = true
72 |
73 | // Capture output
74 | buf := new(bytes.Buffer)
75 | c.SetOut(buf)
76 | c.SetErr(buf)
77 | c.SetArgs(args)
78 |
79 | // Disable execute behavior
80 | c.RunE = func(*cobra.Command, []string) error {
81 | return nil
82 | }
83 |
84 | err := c.Execute()
85 |
86 | return c, buf.String(), err
87 | }
88 |
89 | func TestVersion(t *testing.T) {
90 | data, err := os.ReadFile("version.txt")
91 | if err != nil {
92 | t.Fatalf("failed to read version.txt: %v", err)
93 | }
94 | want := strings.TrimSpace(string(data))
95 |
96 | _, got, err := invokeCommand([]string{"--version"})
97 | if err != nil {
98 | t.Fatalf("error invoking command: %s", err)
99 | }
100 |
101 | if !strings.Contains(got, want) {
102 | t.Errorf("cli did not return correct version: want %q, got %q", want, got)
103 | }
104 | }
105 |
106 | func TestServerConfigFlags(t *testing.T) {
107 | tcs := []struct {
108 | desc string
109 | args []string
110 | want server.ServerConfig
111 | }{
112 | {
113 | desc: "default values",
114 | args: []string{},
115 | want: withDefaults(server.ServerConfig{}),
116 | },
117 | {
118 | desc: "address short",
119 | args: []string{"-a", "127.0.1.1"},
120 | want: withDefaults(server.ServerConfig{
121 | Address: "127.0.1.1",
122 | }),
123 | },
124 | {
125 | desc: "address long",
126 | args: []string{"--address", "0.0.0.0"},
127 | want: withDefaults(server.ServerConfig{
128 | Address: "0.0.0.0",
129 | }),
130 | },
131 | {
132 | desc: "port short",
133 | args: []string{"-p", "5052"},
134 | want: withDefaults(server.ServerConfig{
135 | Port: 5052,
136 | }),
137 | },
138 | {
139 | desc: "port long",
140 | args: []string{"--port", "5050"},
141 | want: withDefaults(server.ServerConfig{
142 | Port: 5050,
143 | }),
144 | },
145 | {
146 | desc: "logging format",
147 | args: []string{"--logging-format", "JSON"},
148 | want: withDefaults(server.ServerConfig{
149 | LoggingFormat: "JSON",
150 | }),
151 | },
152 | {
153 | desc: "debug logs",
154 | args: []string{"--log-level", "WARN"},
155 | want: withDefaults(server.ServerConfig{
156 | LogLevel: "WARN",
157 | }),
158 | },
159 | {
160 | desc: "telemetry gcp",
161 | args: []string{"--telemetry-gcp"},
162 | want: withDefaults(server.ServerConfig{
163 | TelemetryGCP: true,
164 | }),
165 | },
166 | {
167 | desc: "telemetry otlp",
168 | args: []string{"--telemetry-otlp", "http://127.0.0.1:4553"},
169 | want: withDefaults(server.ServerConfig{
170 | TelemetryOTLP: "http://127.0.0.1:4553",
171 | }),
172 | },
173 | {
174 | desc: "telemetry service name",
175 | args: []string{"--telemetry-service-name", "toolbox-custom"},
176 | want: withDefaults(server.ServerConfig{
177 | TelemetryServiceName: "toolbox-custom",
178 | }),
179 | },
180 | {
181 | desc: "stdio",
182 | args: []string{"--stdio"},
183 | want: withDefaults(server.ServerConfig{
184 | Stdio: true,
185 | }),
186 | },
187 | {
188 | desc: "disable reload",
189 | args: []string{"--disable-reload"},
190 | want: withDefaults(server.ServerConfig{
191 | DisableReload: true,
192 | }),
193 | },
194 | }
195 | for _, tc := range tcs {
196 | t.Run(tc.desc, func(t *testing.T) {
197 | c, _, err := invokeCommand(tc.args)
198 | if err != nil {
199 | t.Fatalf("unexpected error invoking command: %s", err)
200 | }
201 |
202 | if !cmp.Equal(c.cfg, tc.want) {
203 | t.Fatalf("got %v, want %v", c.cfg, tc.want)
204 | }
205 | })
206 | }
207 | }
208 |
209 | func TestParseEnv(t *testing.T) {
210 | tcs := []struct {
211 | desc string
212 | env map[string]string
213 | in string
214 | want string
215 | err bool
216 | errString string
217 | }{
218 | {
219 | desc: "without default without env",
220 | in: "${FOO}",
221 | want: "",
222 | err: true,
223 | errString: `environment variable not found: "FOO"`,
224 | },
225 | {
226 | desc: "without default with env",
227 | env: map[string]string{
228 | "FOO": "bar",
229 | },
230 | in: "${FOO}",
231 | want: "bar",
232 | },
233 | {
234 | desc: "with empty default",
235 | in: "${FOO:}",
236 | want: "",
237 | },
238 | {
239 | desc: "with default",
240 | in: "${FOO:bar}",
241 | want: "bar",
242 | },
243 | {
244 | desc: "with default with env",
245 | env: map[string]string{
246 | "FOO": "hello",
247 | },
248 | in: "${FOO:bar}",
249 | want: "hello",
250 | },
251 | }
252 | for _, tc := range tcs {
253 | t.Run(tc.desc, func(t *testing.T) {
254 | if tc.env != nil {
255 | for k, v := range tc.env {
256 | t.Setenv(k, v)
257 | }
258 | }
259 | got, err := parseEnv(tc.in)
260 | if tc.err {
261 | if err == nil {
262 | t.Fatalf("expected error not found")
263 | }
264 | if tc.errString != err.Error() {
265 | t.Fatalf("incorrect error string: got %s, want %s", err, tc.errString)
266 | }
267 | }
268 | if tc.want != got {
269 | t.Fatalf("unexpected want: got %s, want %s", got, tc.want)
270 | }
271 | })
272 | }
273 | }
274 |
275 | func TestToolFileFlag(t *testing.T) {
276 | tcs := []struct {
277 | desc string
278 | args []string
279 | want string
280 | }{
281 | {
282 | desc: "default value",
283 | args: []string{},
284 | want: "",
285 | },
286 | {
287 | desc: "foo file",
288 | args: []string{"--tools-file", "foo.yaml"},
289 | want: "foo.yaml",
290 | },
291 | {
292 | desc: "address long",
293 | args: []string{"--tools-file", "bar.yaml"},
294 | want: "bar.yaml",
295 | },
296 | {
297 | desc: "deprecated flag",
298 | args: []string{"--tools_file", "foo.yaml"},
299 | want: "foo.yaml",
300 | },
301 | }
302 | for _, tc := range tcs {
303 | t.Run(tc.desc, func(t *testing.T) {
304 | c, _, err := invokeCommand(tc.args)
305 | if err != nil {
306 | t.Fatalf("unexpected error invoking command: %s", err)
307 | }
308 | if c.tools_file != tc.want {
309 | t.Fatalf("got %v, want %v", c.cfg, tc.want)
310 | }
311 | })
312 | }
313 | }
314 |
315 | func TestToolsFilesFlag(t *testing.T) {
316 | tcs := []struct {
317 | desc string
318 | args []string
319 | want []string
320 | }{
321 | {
322 | desc: "no value",
323 | args: []string{},
324 | want: []string{},
325 | },
326 | {
327 | desc: "single file",
328 | args: []string{"--tools-files", "foo.yaml"},
329 | want: []string{"foo.yaml"},
330 | },
331 | {
332 | desc: "multiple files",
333 | args: []string{"--tools-files", "foo.yaml,bar.yaml"},
334 | want: []string{"foo.yaml", "bar.yaml"},
335 | },
336 | }
337 | for _, tc := range tcs {
338 | t.Run(tc.desc, func(t *testing.T) {
339 | c, _, err := invokeCommand(tc.args)
340 | if err != nil {
341 | t.Fatalf("unexpected error invoking command: %s", err)
342 | }
343 | if diff := cmp.Diff(c.tools_files, tc.want); diff != "" {
344 | t.Fatalf("got %v, want %v", c.tools_files, tc.want)
345 | }
346 | })
347 | }
348 | }
349 |
350 | func TestToolsFolderFlag(t *testing.T) {
351 | tcs := []struct {
352 | desc string
353 | args []string
354 | want string
355 | }{
356 | {
357 | desc: "no value",
358 | args: []string{},
359 | want: "",
360 | },
361 | {
362 | desc: "folder set",
363 | args: []string{"--tools-folder", "test-folder"},
364 | want: "test-folder",
365 | },
366 | }
367 | for _, tc := range tcs {
368 | t.Run(tc.desc, func(t *testing.T) {
369 | c, _, err := invokeCommand(tc.args)
370 | if err != nil {
371 | t.Fatalf("unexpected error invoking command: %s", err)
372 | }
373 | if c.tools_folder != tc.want {
374 | t.Fatalf("got %v, want %v", c.tools_folder, tc.want)
375 | }
376 | })
377 | }
378 | }
379 |
380 | func TestPrebuiltFlag(t *testing.T) {
381 | tcs := []struct {
382 | desc string
383 | args []string
384 | want string
385 | }{
386 | {
387 | desc: "default value",
388 | args: []string{},
389 | want: "",
390 | },
391 | {
392 | desc: "custom pre built flag",
393 | args: []string{"--tools-file", "alloydb"},
394 | want: "alloydb",
395 | },
396 | }
397 | for _, tc := range tcs {
398 | t.Run(tc.desc, func(t *testing.T) {
399 | c, _, err := invokeCommand(tc.args)
400 | if err != nil {
401 | t.Fatalf("unexpected error invoking command: %s", err)
402 | }
403 | if c.tools_file != tc.want {
404 | t.Fatalf("got %v, want %v", c.cfg, tc.want)
405 | }
406 | })
407 | }
408 | }
409 |
410 | func TestFailServerConfigFlags(t *testing.T) {
411 | tcs := []struct {
412 | desc string
413 | args []string
414 | }{
415 | {
416 | desc: "logging format",
417 | args: []string{"--logging-format", "fail"},
418 | },
419 | {
420 | desc: "debug logs",
421 | args: []string{"--log-level", "fail"},
422 | },
423 | }
424 | for _, tc := range tcs {
425 | t.Run(tc.desc, func(t *testing.T) {
426 | _, _, err := invokeCommand(tc.args)
427 | if err == nil {
428 | t.Fatalf("expected an error, but got nil")
429 | }
430 | })
431 | }
432 | }
433 |
434 | func TestDefaultLoggingFormat(t *testing.T) {
435 | c, _, err := invokeCommand([]string{})
436 | if err != nil {
437 | t.Fatalf("unexpected error invoking command: %s", err)
438 | }
439 | got := c.cfg.LoggingFormat.String()
440 | want := "standard"
441 | if got != want {
442 | t.Fatalf("unexpected default logging format flag: got %v, want %v", got, want)
443 | }
444 | }
445 |
446 | func TestDefaultLogLevel(t *testing.T) {
447 | c, _, err := invokeCommand([]string{})
448 | if err != nil {
449 | t.Fatalf("unexpected error invoking command: %s", err)
450 | }
451 | got := c.cfg.LogLevel.String()
452 | want := "info"
453 | if got != want {
454 | t.Fatalf("unexpected default log level flag: got %v, want %v", got, want)
455 | }
456 | }
457 |
458 | func TestParseToolFile(t *testing.T) {
459 | ctx, err := testutils.ContextWithNewLogger()
460 | if err != nil {
461 | t.Fatalf("unexpected error: %s", err)
462 | }
463 | tcs := []struct {
464 | description string
465 | in string
466 | wantToolsFile ToolsFile
467 | }{
468 | {
469 | description: "basic example",
470 | in: `
471 | sources:
472 | my-pg-instance:
473 | kind: cloud-sql-postgres
474 | project: my-project
475 | region: my-region
476 | instance: my-instance
477 | database: my_db
478 | user: my_user
479 | password: my_pass
480 | tools:
481 | example_tool:
482 | kind: postgres-sql
483 | source: my-pg-instance
484 | description: some description
485 | statement: |
486 | SELECT * FROM SQL_STATEMENT;
487 | parameters:
488 | - name: country
489 | type: string
490 | description: some description
491 | toolsets:
492 | example_toolset:
493 | - example_tool
494 | `,
495 | wantToolsFile: ToolsFile{
496 | Sources: server.SourceConfigs{
497 | "my-pg-instance": cloudsqlpgsrc.Config{
498 | Name: "my-pg-instance",
499 | Kind: cloudsqlpgsrc.SourceKind,
500 | Project: "my-project",
501 | Region: "my-region",
502 | Instance: "my-instance",
503 | IPType: "public",
504 | Database: "my_db",
505 | User: "my_user",
506 | Password: "my_pass",
507 | },
508 | },
509 | Tools: server.ToolConfigs{
510 | "example_tool": postgressql.Config{
511 | Name: "example_tool",
512 | Kind: "postgres-sql",
513 | Source: "my-pg-instance",
514 | Description: "some description",
515 | Statement: "SELECT * FROM SQL_STATEMENT;\n",
516 | Parameters: []tools.Parameter{
517 | tools.NewStringParameter("country", "some description"),
518 | },
519 | AuthRequired: []string{},
520 | },
521 | },
522 | Toolsets: server.ToolsetConfigs{
523 | "example_toolset": tools.ToolsetConfig{
524 | Name: "example_toolset",
525 | ToolNames: []string{"example_tool"},
526 | },
527 | },
528 | },
529 | },
530 | }
531 | for _, tc := range tcs {
532 | t.Run(tc.description, func(t *testing.T) {
533 | toolsFile, err := parseToolsFile(ctx, testutils.FormatYaml(tc.in))
534 | if err != nil {
535 | t.Fatalf("failed to parse input: %v", err)
536 | }
537 | if diff := cmp.Diff(tc.wantToolsFile.Sources, toolsFile.Sources); diff != "" {
538 | t.Fatalf("incorrect sources parse: diff %v", diff)
539 | }
540 | if diff := cmp.Diff(tc.wantToolsFile.AuthServices, toolsFile.AuthServices); diff != "" {
541 | t.Fatalf("incorrect authServices parse: diff %v", diff)
542 | }
543 | if diff := cmp.Diff(tc.wantToolsFile.Tools, toolsFile.Tools); diff != "" {
544 | t.Fatalf("incorrect tools parse: diff %v", diff)
545 | }
546 | if diff := cmp.Diff(tc.wantToolsFile.Toolsets, toolsFile.Toolsets); diff != "" {
547 | t.Fatalf("incorrect tools parse: diff %v", diff)
548 | }
549 | })
550 | }
551 |
552 | }
553 |
554 | func TestParseToolFileWithAuth(t *testing.T) {
555 | ctx, err := testutils.ContextWithNewLogger()
556 | if err != nil {
557 | t.Fatalf("unexpected error: %s", err)
558 | }
559 | tcs := []struct {
560 | description string
561 | in string
562 | wantToolsFile ToolsFile
563 | }{
564 | {
565 | description: "basic example",
566 | in: `
567 | sources:
568 | my-pg-instance:
569 | kind: cloud-sql-postgres
570 | project: my-project
571 | region: my-region
572 | instance: my-instance
573 | database: my_db
574 | user: my_user
575 | password: my_pass
576 | authServices:
577 | my-google-service:
578 | kind: google
579 | clientId: my-client-id
580 | other-google-service:
581 | kind: google
582 | clientId: other-client-id
583 |
584 | tools:
585 | example_tool:
586 | kind: postgres-sql
587 | source: my-pg-instance
588 | description: some description
589 | statement: |
590 | SELECT * FROM SQL_STATEMENT;
591 | parameters:
592 | - name: country
593 | type: string
594 | description: some description
595 | - name: id
596 | type: integer
597 | description: user id
598 | authServices:
599 | - name: my-google-service
600 | field: user_id
601 | - name: email
602 | type: string
603 | description: user email
604 | authServices:
605 | - name: my-google-service
606 | field: email
607 | - name: other-google-service
608 | field: other_email
609 |
610 | toolsets:
611 | example_toolset:
612 | - example_tool
613 | `,
614 | wantToolsFile: ToolsFile{
615 | Sources: server.SourceConfigs{
616 | "my-pg-instance": cloudsqlpgsrc.Config{
617 | Name: "my-pg-instance",
618 | Kind: cloudsqlpgsrc.SourceKind,
619 | Project: "my-project",
620 | Region: "my-region",
621 | Instance: "my-instance",
622 | IPType: "public",
623 | Database: "my_db",
624 | User: "my_user",
625 | Password: "my_pass",
626 | },
627 | },
628 | AuthServices: server.AuthServiceConfigs{
629 | "my-google-service": google.Config{
630 | Name: "my-google-service",
631 | Kind: google.AuthServiceKind,
632 | ClientID: "my-client-id",
633 | },
634 | "other-google-service": google.Config{
635 | Name: "other-google-service",
636 | Kind: google.AuthServiceKind,
637 | ClientID: "other-client-id",
638 | },
639 | },
640 | Tools: server.ToolConfigs{
641 | "example_tool": postgressql.Config{
642 | Name: "example_tool",
643 | Kind: "postgres-sql",
644 | Source: "my-pg-instance",
645 | Description: "some description",
646 | Statement: "SELECT * FROM SQL_STATEMENT;\n",
647 | AuthRequired: []string{},
648 | Parameters: []tools.Parameter{
649 | tools.NewStringParameter("country", "some description"),
650 | tools.NewIntParameterWithAuth("id", "user id", []tools.ParamAuthService{{Name: "my-google-service", Field: "user_id"}}),
651 | tools.NewStringParameterWithAuth("email", "user email", []tools.ParamAuthService{{Name: "my-google-service", Field: "email"}, {Name: "other-google-service", Field: "other_email"}}),
652 | },
653 | },
654 | },
655 | Toolsets: server.ToolsetConfigs{
656 | "example_toolset": tools.ToolsetConfig{
657 | Name: "example_toolset",
658 | ToolNames: []string{"example_tool"},
659 | },
660 | },
661 | },
662 | },
663 | {
664 | description: "basic example with authSources",
665 | in: `
666 | sources:
667 | my-pg-instance:
668 | kind: cloud-sql-postgres
669 | project: my-project
670 | region: my-region
671 | instance: my-instance
672 | database: my_db
673 | user: my_user
674 | password: my_pass
675 | authSources:
676 | my-google-service:
677 | kind: google
678 | clientId: my-client-id
679 | other-google-service:
680 | kind: google
681 | clientId: other-client-id
682 |
683 | tools:
684 | example_tool:
685 | kind: postgres-sql
686 | source: my-pg-instance
687 | description: some description
688 | statement: |
689 | SELECT * FROM SQL_STATEMENT;
690 | parameters:
691 | - name: country
692 | type: string
693 | description: some description
694 | - name: id
695 | type: integer
696 | description: user id
697 | authSources:
698 | - name: my-google-service
699 | field: user_id
700 | - name: email
701 | type: string
702 | description: user email
703 | authSources:
704 | - name: my-google-service
705 | field: email
706 | - name: other-google-service
707 | field: other_email
708 |
709 | toolsets:
710 | example_toolset:
711 | - example_tool
712 | `,
713 | wantToolsFile: ToolsFile{
714 | Sources: server.SourceConfigs{
715 | "my-pg-instance": cloudsqlpgsrc.Config{
716 | Name: "my-pg-instance",
717 | Kind: cloudsqlpgsrc.SourceKind,
718 | Project: "my-project",
719 | Region: "my-region",
720 | Instance: "my-instance",
721 | IPType: "public",
722 | Database: "my_db",
723 | User: "my_user",
724 | Password: "my_pass",
725 | },
726 | },
727 | AuthSources: server.AuthServiceConfigs{
728 | "my-google-service": google.Config{
729 | Name: "my-google-service",
730 | Kind: google.AuthServiceKind,
731 | ClientID: "my-client-id",
732 | },
733 | "other-google-service": google.Config{
734 | Name: "other-google-service",
735 | Kind: google.AuthServiceKind,
736 | ClientID: "other-client-id",
737 | },
738 | },
739 | Tools: server.ToolConfigs{
740 | "example_tool": postgressql.Config{
741 | Name: "example_tool",
742 | Kind: "postgres-sql",
743 | Source: "my-pg-instance",
744 | Description: "some description",
745 | Statement: "SELECT * FROM SQL_STATEMENT;\n",
746 | AuthRequired: []string{},
747 | Parameters: []tools.Parameter{
748 | tools.NewStringParameter("country", "some description"),
749 | tools.NewIntParameterWithAuth("id", "user id", []tools.ParamAuthService{{Name: "my-google-service", Field: "user_id"}}),
750 | tools.NewStringParameterWithAuth("email", "user email", []tools.ParamAuthService{{Name: "my-google-service", Field: "email"}, {Name: "other-google-service", Field: "other_email"}}),
751 | },
752 | },
753 | },
754 | Toolsets: server.ToolsetConfigs{
755 | "example_toolset": tools.ToolsetConfig{
756 | Name: "example_toolset",
757 | ToolNames: []string{"example_tool"},
758 | },
759 | },
760 | },
761 | },
762 | {
763 | description: "basic example with authRequired",
764 | in: `
765 | sources:
766 | my-pg-instance:
767 | kind: cloud-sql-postgres
768 | project: my-project
769 | region: my-region
770 | instance: my-instance
771 | database: my_db
772 | user: my_user
773 | password: my_pass
774 | authServices:
775 | my-google-service:
776 | kind: google
777 | clientId: my-client-id
778 | other-google-service:
779 | kind: google
780 | clientId: other-client-id
781 |
782 | tools:
783 | example_tool:
784 | kind: postgres-sql
785 | source: my-pg-instance
786 | description: some description
787 | statement: |
788 | SELECT * FROM SQL_STATEMENT;
789 | authRequired:
790 | - my-google-service
791 | parameters:
792 | - name: country
793 | type: string
794 | description: some description
795 | - name: id
796 | type: integer
797 | description: user id
798 | authServices:
799 | - name: my-google-service
800 | field: user_id
801 | - name: email
802 | type: string
803 | description: user email
804 | authServices:
805 | - name: my-google-service
806 | field: email
807 | - name: other-google-service
808 | field: other_email
809 |
810 | toolsets:
811 | example_toolset:
812 | - example_tool
813 | `,
814 | wantToolsFile: ToolsFile{
815 | Sources: server.SourceConfigs{
816 | "my-pg-instance": cloudsqlpgsrc.Config{
817 | Name: "my-pg-instance",
818 | Kind: cloudsqlpgsrc.SourceKind,
819 | Project: "my-project",
820 | Region: "my-region",
821 | Instance: "my-instance",
822 | IPType: "public",
823 | Database: "my_db",
824 | User: "my_user",
825 | Password: "my_pass",
826 | },
827 | },
828 | AuthServices: server.AuthServiceConfigs{
829 | "my-google-service": google.Config{
830 | Name: "my-google-service",
831 | Kind: google.AuthServiceKind,
832 | ClientID: "my-client-id",
833 | },
834 | "other-google-service": google.Config{
835 | Name: "other-google-service",
836 | Kind: google.AuthServiceKind,
837 | ClientID: "other-client-id",
838 | },
839 | },
840 | Tools: server.ToolConfigs{
841 | "example_tool": postgressql.Config{
842 | Name: "example_tool",
843 | Kind: "postgres-sql",
844 | Source: "my-pg-instance",
845 | Description: "some description",
846 | Statement: "SELECT * FROM SQL_STATEMENT;\n",
847 | AuthRequired: []string{"my-google-service"},
848 | Parameters: []tools.Parameter{
849 | tools.NewStringParameter("country", "some description"),
850 | tools.NewIntParameterWithAuth("id", "user id", []tools.ParamAuthService{{Name: "my-google-service", Field: "user_id"}}),
851 | tools.NewStringParameterWithAuth("email", "user email", []tools.ParamAuthService{{Name: "my-google-service", Field: "email"}, {Name: "other-google-service", Field: "other_email"}}),
852 | },
853 | },
854 | },
855 | Toolsets: server.ToolsetConfigs{
856 | "example_toolset": tools.ToolsetConfig{
857 | Name: "example_toolset",
858 | ToolNames: []string{"example_tool"},
859 | },
860 | },
861 | },
862 | },
863 | }
864 | for _, tc := range tcs {
865 | t.Run(tc.description, func(t *testing.T) {
866 | toolsFile, err := parseToolsFile(ctx, testutils.FormatYaml(tc.in))
867 | if err != nil {
868 | t.Fatalf("failed to parse input: %v", err)
869 | }
870 | if diff := cmp.Diff(tc.wantToolsFile.Sources, toolsFile.Sources); diff != "" {
871 | t.Fatalf("incorrect sources parse: diff %v", diff)
872 | }
873 | if diff := cmp.Diff(tc.wantToolsFile.AuthServices, toolsFile.AuthServices); diff != "" {
874 | t.Fatalf("incorrect authServices parse: diff %v", diff)
875 | }
876 | if diff := cmp.Diff(tc.wantToolsFile.Tools, toolsFile.Tools); diff != "" {
877 | t.Fatalf("incorrect tools parse: diff %v", diff)
878 | }
879 | if diff := cmp.Diff(tc.wantToolsFile.Toolsets, toolsFile.Toolsets); diff != "" {
880 | t.Fatalf("incorrect tools parse: diff %v", diff)
881 | }
882 | })
883 | }
884 |
885 | }
886 |
887 | func TestEnvVarReplacement(t *testing.T) {
888 | ctx, err := testutils.ContextWithNewLogger()
889 | t.Setenv("TestHeader", "ACTUAL_HEADER")
890 | t.Setenv("API_KEY", "ACTUAL_API_KEY")
891 | t.Setenv("clientId", "ACTUAL_CLIENT_ID")
892 | t.Setenv("clientId2", "ACTUAL_CLIENT_ID_2")
893 | t.Setenv("toolset_name", "ACTUAL_TOOLSET_NAME")
894 | t.Setenv("cat_string", "cat")
895 | t.Setenv("food_string", "food")
896 | t.Setenv("TestHeader", "ACTUAL_HEADER")
897 |
898 | if err != nil {
899 | t.Fatalf("unexpected error: %s", err)
900 | }
901 | tcs := []struct {
902 | description string
903 | in string
904 | wantToolsFile ToolsFile
905 | }{
906 | {
907 | description: "file with env var example",
908 | in: `
909 | sources:
910 | my-http-instance:
911 | kind: http
912 | baseUrl: http://test_server/
913 | timeout: 10s
914 | headers:
915 | Authorization: ${TestHeader}
916 | queryParams:
917 | api-key: ${API_KEY}
918 | authServices:
919 | my-google-service:
920 | kind: google
921 | clientId: ${clientId}
922 | other-google-service:
923 | kind: google
924 | clientId: ${clientId2}
925 |
926 | tools:
927 | example_tool:
928 | kind: http
929 | source: my-instance
930 | method: GET
931 | path: "search?name=alice&pet=${cat_string}"
932 | description: some description
933 | authRequired:
934 | - my-google-auth-service
935 | - other-auth-service
936 | queryParams:
937 | - name: country
938 | type: string
939 | description: some description
940 | authServices:
941 | - name: my-google-auth-service
942 | field: user_id
943 | - name: other-auth-service
944 | field: user_id
945 | requestBody: |
946 | {
947 | "age": {{.age}},
948 | "city": "{{.city}}",
949 | "food": "${food_string}",
950 | "other": "$OTHER"
951 | }
952 | bodyParams:
953 | - name: age
954 | type: integer
955 | description: age num
956 | - name: city
957 | type: string
958 | description: city string
959 | headers:
960 | Authorization: API_KEY
961 | Content-Type: application/json
962 | headerParams:
963 | - name: Language
964 | type: string
965 | description: language string
966 |
967 | toolsets:
968 | ${toolset_name}:
969 | - example_tool
970 | `,
971 | wantToolsFile: ToolsFile{
972 | Sources: server.SourceConfigs{
973 | "my-http-instance": httpsrc.Config{
974 | Name: "my-http-instance",
975 | Kind: httpsrc.SourceKind,
976 | BaseURL: "http://test_server/",
977 | Timeout: "10s",
978 | DefaultHeaders: map[string]string{"Authorization": "ACTUAL_HEADER"},
979 | QueryParams: map[string]string{"api-key": "ACTUAL_API_KEY"},
980 | },
981 | },
982 | AuthServices: server.AuthServiceConfigs{
983 | "my-google-service": google.Config{
984 | Name: "my-google-service",
985 | Kind: google.AuthServiceKind,
986 | ClientID: "ACTUAL_CLIENT_ID",
987 | },
988 | "other-google-service": google.Config{
989 | Name: "other-google-service",
990 | Kind: google.AuthServiceKind,
991 | ClientID: "ACTUAL_CLIENT_ID_2",
992 | },
993 | },
994 | Tools: server.ToolConfigs{
995 | "example_tool": http.Config{
996 | Name: "example_tool",
997 | Kind: "http",
998 | Source: "my-instance",
999 | Method: "GET",
1000 | Path: "search?name=alice&pet=cat",
1001 | Description: "some description",
1002 | AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
1003 | QueryParams: []tools.Parameter{
1004 | tools.NewStringParameterWithAuth("country", "some description",
1005 | []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
1006 | {Name: "other-auth-service", Field: "user_id"}}),
1007 | },
1008 | RequestBody: `{
1009 | "age": {{.age}},
1010 | "city": "{{.city}}",
1011 | "food": "food",
1012 | "other": "$OTHER"
1013 | }
1014 | `,
1015 | BodyParams: []tools.Parameter{tools.NewIntParameter("age", "age num"), tools.NewStringParameter("city", "city string")},
1016 | Headers: map[string]string{"Authorization": "API_KEY", "Content-Type": "application/json"},
1017 | HeaderParams: []tools.Parameter{tools.NewStringParameter("Language", "language string")},
1018 | },
1019 | },
1020 | Toolsets: server.ToolsetConfigs{
1021 | "ACTUAL_TOOLSET_NAME": tools.ToolsetConfig{
1022 | Name: "ACTUAL_TOOLSET_NAME",
1023 | ToolNames: []string{"example_tool"},
1024 | },
1025 | },
1026 | },
1027 | },
1028 | }
1029 | for _, tc := range tcs {
1030 | t.Run(tc.description, func(t *testing.T) {
1031 | toolsFile, err := parseToolsFile(ctx, testutils.FormatYaml(tc.in))
1032 | if err != nil {
1033 | t.Fatalf("failed to parse input: %v", err)
1034 | }
1035 | if diff := cmp.Diff(tc.wantToolsFile.Sources, toolsFile.Sources); diff != "" {
1036 | t.Fatalf("incorrect sources parse: diff %v", diff)
1037 | }
1038 | if diff := cmp.Diff(tc.wantToolsFile.AuthServices, toolsFile.AuthServices); diff != "" {
1039 | t.Fatalf("incorrect authServices parse: diff %v", diff)
1040 | }
1041 | if diff := cmp.Diff(tc.wantToolsFile.Tools, toolsFile.Tools); diff != "" {
1042 | t.Fatalf("incorrect tools parse: diff %v", diff)
1043 | }
1044 | if diff := cmp.Diff(tc.wantToolsFile.Toolsets, toolsFile.Toolsets); diff != "" {
1045 | t.Fatalf("incorrect tools parse: diff %v", diff)
1046 | }
1047 | })
1048 | }
1049 |
1050 | }
1051 |
1052 | // normalizeFilepaths is a helper function to allow same filepath formats for Mac and Windows.
1053 | // this prevents needing multiple "want" cases for TestResolveWatcherInputs
1054 | func normalizeFilepaths(m map[string]bool) map[string]bool {
1055 | newMap := make(map[string]bool)
1056 | for k, v := range m {
1057 | newMap[filepath.ToSlash(k)] = v
1058 | }
1059 | return newMap
1060 | }
1061 |
1062 | func TestResolveWatcherInputs(t *testing.T) {
1063 | tcs := []struct {
1064 | description string
1065 | toolsFile string
1066 | toolsFiles []string
1067 | toolsFolder string
1068 | wantWatchDirs map[string]bool
1069 | wantWatchedFiles map[string]bool
1070 | }{
1071 | {
1072 | description: "single tools file",
1073 | toolsFile: "tools_folder/example_tools.yaml",
1074 | toolsFiles: []string{},
1075 | toolsFolder: "",
1076 | wantWatchDirs: map[string]bool{"tools_folder": true},
1077 | wantWatchedFiles: map[string]bool{"tools_folder/example_tools.yaml": true},
1078 | },
1079 | {
1080 | description: "default tools file (root dir)",
1081 | toolsFile: "tools.yaml",
1082 | toolsFiles: []string{},
1083 | toolsFolder: "",
1084 | wantWatchDirs: map[string]bool{".": true},
1085 | wantWatchedFiles: map[string]bool{"tools.yaml": true},
1086 | },
1087 | {
1088 | description: "multiple files in different folders",
1089 | toolsFile: "",
1090 | toolsFiles: []string{"tools_folder/example_tools.yaml", "tools_folder2/example_tools.yaml"},
1091 | toolsFolder: "",
1092 | wantWatchDirs: map[string]bool{"tools_folder": true, "tools_folder2": true},
1093 | wantWatchedFiles: map[string]bool{
1094 | "tools_folder/example_tools.yaml": true,
1095 | "tools_folder2/example_tools.yaml": true,
1096 | },
1097 | },
1098 | {
1099 | description: "multiple files in same folder",
1100 | toolsFile: "",
1101 | toolsFiles: []string{"tools_folder/example_tools.yaml", "tools_folder/example_tools2.yaml"},
1102 | toolsFolder: "",
1103 | wantWatchDirs: map[string]bool{"tools_folder": true},
1104 | wantWatchedFiles: map[string]bool{
1105 | "tools_folder/example_tools.yaml": true,
1106 | "tools_folder/example_tools2.yaml": true,
1107 | },
1108 | },
1109 | {
1110 | description: "multiple files in different levels",
1111 | toolsFile: "",
1112 | toolsFiles: []string{
1113 | "tools_folder/example_tools.yaml",
1114 | "tools_folder/special_tools/example_tools2.yaml"},
1115 | toolsFolder: "",
1116 | wantWatchDirs: map[string]bool{"tools_folder": true, "tools_folder/special_tools": true},
1117 | wantWatchedFiles: map[string]bool{
1118 | "tools_folder/example_tools.yaml": true,
1119 | "tools_folder/special_tools/example_tools2.yaml": true,
1120 | },
1121 | },
1122 | {
1123 | description: "tools folder",
1124 | toolsFile: "",
1125 | toolsFiles: []string{},
1126 | toolsFolder: "tools_folder",
1127 | wantWatchDirs: map[string]bool{"tools_folder": true},
1128 | wantWatchedFiles: map[string]bool{},
1129 | },
1130 | }
1131 | for _, tc := range tcs {
1132 | t.Run(tc.description, func(t *testing.T) {
1133 | gotWatchDirs, gotWatchedFiles := resolveWatcherInputs(tc.toolsFile, tc.toolsFiles, tc.toolsFolder)
1134 |
1135 | normalizedGotWatchDirs := normalizeFilepaths(gotWatchDirs)
1136 | normalizedGotWatchedFiles := normalizeFilepaths(gotWatchedFiles)
1137 |
1138 | if diff := cmp.Diff(tc.wantWatchDirs, normalizedGotWatchDirs); diff != "" {
1139 | t.Errorf("incorrect watchDirs: diff %v", diff)
1140 | }
1141 | if diff := cmp.Diff(tc.wantWatchedFiles, normalizedGotWatchedFiles); diff != "" {
1142 | t.Errorf("incorrect watchedFiles: diff %v", diff)
1143 | }
1144 |
1145 | })
1146 | }
1147 | }
1148 |
1149 | // helper function for testing file detection in dynamic reloading
1150 | func tmpFileWithCleanup(content []byte) (string, func(), error) {
1151 | f, err := os.CreateTemp("", "*")
1152 | if err != nil {
1153 | return "", nil, err
1154 | }
1155 | cleanup := func() { os.Remove(f.Name()) }
1156 |
1157 | if _, err := f.Write(content); err != nil {
1158 | cleanup()
1159 | return "", nil, err
1160 | }
1161 | if err := f.Close(); err != nil {
1162 | cleanup()
1163 | return "", nil, err
1164 | }
1165 | return f.Name(), cleanup, err
1166 | }
1167 |
1168 | func TestSingleEdit(t *testing.T) {
1169 | ctx, cancelCtx := context.WithTimeout(context.Background(), time.Minute)
1170 | defer cancelCtx()
1171 |
1172 | pr, pw := io.Pipe()
1173 | defer pw.Close()
1174 | defer pr.Close()
1175 |
1176 | fileToWatch, cleanup, err := tmpFileWithCleanup([]byte("initial content"))
1177 | if err != nil {
1178 | t.Fatalf("error editing tools file %s", err)
1179 | }
1180 | defer cleanup()
1181 |
1182 | logger, err := log.NewStdLogger(pw, pw, "DEBUG")
1183 | if err != nil {
1184 | t.Fatalf("failed to setup logger %s", err)
1185 | }
1186 | ctx = util.WithLogger(ctx, logger)
1187 |
1188 | instrumentation, err := telemetry.CreateTelemetryInstrumentation(versionString)
1189 | if err != nil {
1190 | t.Fatalf("failed to setup instrumentation %s", err)
1191 | }
1192 | ctx = util.WithInstrumentation(ctx, instrumentation)
1193 |
1194 | mockServer := &server.Server{}
1195 |
1196 | cleanFileToWatch := filepath.Clean(fileToWatch)
1197 | watchDir := filepath.Dir(cleanFileToWatch)
1198 |
1199 | watchedFiles := map[string]bool{cleanFileToWatch: true}
1200 | watchDirs := map[string]bool{watchDir: true}
1201 |
1202 | go watchChanges(ctx, watchDirs, watchedFiles, mockServer)
1203 |
1204 | // escape backslash so regex doesn't fail on windows filepaths
1205 | regexEscapedPathFile := strings.ReplaceAll(cleanFileToWatch, `\`, `\\\\*\\`)
1206 | regexEscapedPathFile = path.Clean(regexEscapedPathFile)
1207 |
1208 | regexEscapedPathDir := strings.ReplaceAll(watchDir, `\`, `\\\\*\\`)
1209 | regexEscapedPathDir = path.Clean(regexEscapedPathDir)
1210 |
1211 | begunWatchingDir := regexp.MustCompile(fmt.Sprintf(`DEBUG "Added directory %s to watcher."`, regexEscapedPathDir))
1212 | _, err = testutils.WaitForString(ctx, begunWatchingDir, pr)
1213 | if err != nil {
1214 | t.Fatalf("timeout or error waiting for watcher to start: %s", err)
1215 | }
1216 |
1217 | err = os.WriteFile(fileToWatch, []byte("modification"), 0777)
1218 | if err != nil {
1219 | t.Fatalf("error writing to file: %v", err)
1220 | }
1221 |
1222 | // only check substring of DEBUG message due to some OS/editors firing different operations
1223 | detectedFileChange := regexp.MustCompile(fmt.Sprintf(`event detected in %s"`, regexEscapedPathFile))
1224 | _, err = testutils.WaitForString(ctx, detectedFileChange, pr)
1225 | if err != nil {
1226 | t.Fatalf("timeout or error waiting for file to detect write: %s", err)
1227 | }
1228 | }
1229 |
1230 | func TestPrebuiltTools(t *testing.T) {
1231 | // Get prebuilt configs
1232 | alloydb_admin_config, _ := prebuiltconfigs.Get("alloydb-postgres-admin")
1233 | alloydb_config, _ := prebuiltconfigs.Get("alloydb-postgres")
1234 | bigquery_config, _ := prebuiltconfigs.Get("bigquery")
1235 | clickhouse_config, _ := prebuiltconfigs.Get("clickhouse")
1236 | cloudsqlpg_config, _ := prebuiltconfigs.Get("cloud-sql-postgres")
1237 | cloudsqlpg_admin_config, _ := prebuiltconfigs.Get("cloud-sql-postgres-admin")
1238 | cloudsqlmysql_config, _ := prebuiltconfigs.Get("cloud-sql-mysql")
1239 | cloudsqlmysql_admin_config, _ := prebuiltconfigs.Get("cloud-sql-mysql-admin")
1240 | cloudsqlmssql_config, _ := prebuiltconfigs.Get("cloud-sql-mssql")
1241 | cloudsqlmssql_admin_config, _ := prebuiltconfigs.Get("cloud-sql-mssql-admin")
1242 | dataplex_config, _ := prebuiltconfigs.Get("dataplex")
1243 | firestoreconfig, _ := prebuiltconfigs.Get("firestore")
1244 | mysql_config, _ := prebuiltconfigs.Get("mysql")
1245 | mssql_config, _ := prebuiltconfigs.Get("mssql")
1246 | looker_config, _ := prebuiltconfigs.Get("looker")
1247 | lookerca_config, _ := prebuiltconfigs.Get("looker-conversational-analytics")
1248 | postgresconfig, _ := prebuiltconfigs.Get("postgres")
1249 | spanner_config, _ := prebuiltconfigs.Get("spanner")
1250 | spannerpg_config, _ := prebuiltconfigs.Get("spanner-postgres")
1251 | mindsdb_config, _ := prebuiltconfigs.Get("mindsdb")
1252 | sqlite_config, _ := prebuiltconfigs.Get("sqlite")
1253 | neo4jconfig, _ := prebuiltconfigs.Get("neo4j")
1254 | alloydbobsvconfig, _ := prebuiltconfigs.Get("alloydb-postgres-observability")
1255 | cloudsqlpgobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-postgres-observability")
1256 | cloudsqlmysqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mysql-observability")
1257 | cloudsqlmssqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mssql-observability")
1258 | serverless_spark_config, _ := prebuiltconfigs.Get("serverless-spark")
1259 | cloudhealthcare_config, _ := prebuiltconfigs.Get("cloud-healthcare")
1260 |
1261 | // Set environment variables
1262 | t.Setenv("API_KEY", "your_api_key")
1263 |
1264 | t.Setenv("BIGQUERY_PROJECT", "your_gcp_project_id")
1265 | t.Setenv("DATAPLEX_PROJECT", "your_gcp_project_id")
1266 | t.Setenv("FIRESTORE_PROJECT", "your_gcp_project_id")
1267 | t.Setenv("FIRESTORE_DATABASE", "your_firestore_db_name")
1268 |
1269 | t.Setenv("SPANNER_PROJECT", "your_gcp_project_id")
1270 | t.Setenv("SPANNER_INSTANCE", "your_spanner_instance")
1271 | t.Setenv("SPANNER_DATABASE", "your_spanner_db")
1272 |
1273 | t.Setenv("ALLOYDB_POSTGRES_PROJECT", "your_gcp_project_id")
1274 | t.Setenv("ALLOYDB_POSTGRES_REGION", "your_gcp_region")
1275 | t.Setenv("ALLOYDB_POSTGRES_CLUSTER", "your_alloydb_cluster")
1276 | t.Setenv("ALLOYDB_POSTGRES_INSTANCE", "your_alloydb_instance")
1277 | t.Setenv("ALLOYDB_POSTGRES_DATABASE", "your_alloydb_db")
1278 | t.Setenv("ALLOYDB_POSTGRES_USER", "your_alloydb_user")
1279 | t.Setenv("ALLOYDB_POSTGRES_PASSWORD", "your_alloydb_password")
1280 |
1281 | t.Setenv("CLICKHOUSE_PROTOCOL", "your_clickhouse_protocol")
1282 | t.Setenv("CLICKHOUSE_DATABASE", "your_clickhouse_database")
1283 | t.Setenv("CLICKHOUSE_PASSWORD", "your_clickhouse_password")
1284 | t.Setenv("CLICKHOUSE_USER", "your_clickhouse_user")
1285 | t.Setenv("CLICKHOUSE_HOST", "your_clickhosue_host")
1286 | t.Setenv("CLICKHOUSE_PORT", "8123")
1287 |
1288 | t.Setenv("CLOUD_SQL_POSTGRES_PROJECT", "your_pg_project")
1289 | t.Setenv("CLOUD_SQL_POSTGRES_INSTANCE", "your_pg_instance")
1290 | t.Setenv("CLOUD_SQL_POSTGRES_DATABASE", "your_pg_db")
1291 | t.Setenv("CLOUD_SQL_POSTGRES_REGION", "your_pg_region")
1292 | t.Setenv("CLOUD_SQL_POSTGRES_USER", "your_pg_user")
1293 | t.Setenv("CLOUD_SQL_POSTGRES_PASS", "your_pg_pass")
1294 |
1295 | t.Setenv("CLOUD_SQL_MYSQL_PROJECT", "your_gcp_project_id")
1296 | t.Setenv("CLOUD_SQL_MYSQL_REGION", "your_gcp_region")
1297 | t.Setenv("CLOUD_SQL_MYSQL_INSTANCE", "your_instance")
1298 | t.Setenv("CLOUD_SQL_MYSQL_DATABASE", "your_cloudsql_mysql_db")
1299 | t.Setenv("CLOUD_SQL_MYSQL_USER", "your_cloudsql_mysql_user")
1300 | t.Setenv("CLOUD_SQL_MYSQL_PASSWORD", "your_cloudsql_mysql_password")
1301 |
1302 | t.Setenv("CLOUD_SQL_MSSQL_PROJECT", "your_gcp_project_id")
1303 | t.Setenv("CLOUD_SQL_MSSQL_REGION", "your_gcp_region")
1304 | t.Setenv("CLOUD_SQL_MSSQL_INSTANCE", "your_cloudsql_mssql_instance")
1305 | t.Setenv("CLOUD_SQL_MSSQL_DATABASE", "your_cloudsql_mssql_db")
1306 | t.Setenv("CLOUD_SQL_MSSQL_IP_ADDRESS", "127.0.0.1")
1307 | t.Setenv("CLOUD_SQL_MSSQL_USER", "your_cloudsql_mssql_user")
1308 | t.Setenv("CLOUD_SQL_MSSQL_PASSWORD", "your_cloudsql_mssql_password")
1309 | t.Setenv("CLOUD_SQL_POSTGRES_PASSWORD", "your_cloudsql_pg_password")
1310 |
1311 | t.Setenv("SERVERLESS_SPARK_PROJECT", "your_gcp_project_id")
1312 | t.Setenv("SERVERLESS_SPARK_LOCATION", "your_gcp_location")
1313 |
1314 | t.Setenv("POSTGRES_HOST", "localhost")
1315 | t.Setenv("POSTGRES_PORT", "5432")
1316 | t.Setenv("POSTGRES_DATABASE", "your_postgres_db")
1317 | t.Setenv("POSTGRES_USER", "your_postgres_user")
1318 | t.Setenv("POSTGRES_PASSWORD", "your_postgres_password")
1319 |
1320 | t.Setenv("MYSQL_HOST", "localhost")
1321 | t.Setenv("MYSQL_PORT", "3306")
1322 | t.Setenv("MYSQL_DATABASE", "your_mysql_db")
1323 | t.Setenv("MYSQL_USER", "your_mysql_user")
1324 | t.Setenv("MYSQL_PASSWORD", "your_mysql_password")
1325 |
1326 | t.Setenv("MSSQL_HOST", "localhost")
1327 | t.Setenv("MSSQL_PORT", "1433")
1328 | t.Setenv("MSSQL_DATABASE", "your_mssql_db")
1329 | t.Setenv("MSSQL_USER", "your_mssql_user")
1330 | t.Setenv("MSSQL_PASSWORD", "your_mssql_password")
1331 |
1332 | t.Setenv("MINDSDB_HOST", "localhost")
1333 | t.Setenv("MINDSDB_PORT", "47334")
1334 | t.Setenv("MINDSDB_DATABASE", "your_mindsdb_db")
1335 | t.Setenv("MINDSDB_USER", "your_mindsdb_user")
1336 | t.Setenv("MINDSDB_PASS", "your_mindsdb_password")
1337 |
1338 | t.Setenv("LOOKER_BASE_URL", "https://your_company.looker.com")
1339 | t.Setenv("LOOKER_CLIENT_ID", "your_looker_client_id")
1340 | t.Setenv("LOOKER_CLIENT_SECRET", "your_looker_client_secret")
1341 | t.Setenv("LOOKER_VERIFY_SSL", "true")
1342 |
1343 | t.Setenv("LOOKER_PROJECT", "your_project_id")
1344 | t.Setenv("LOOKER_LOCATION", "us")
1345 |
1346 | t.Setenv("SQLITE_DATABASE", "test.db")
1347 |
1348 | t.Setenv("NEO4J_URI", "bolt://localhost:7687")
1349 | t.Setenv("NEO4J_DATABASE", "neo4j")
1350 | t.Setenv("NEO4J_USERNAME", "your_neo4j_user")
1351 | t.Setenv("NEO4J_PASSWORD", "your_neo4j_password")
1352 |
1353 | t.Setenv("CLOUD_HEALTHCARE_PROJECT", "your_gcp_project_id")
1354 | t.Setenv("CLOUD_HEALTHCARE_REGION", "your_gcp_region")
1355 | t.Setenv("CLOUD_HEALTHCARE_DATASET", "your_healthcare_dataset")
1356 |
1357 | ctx, err := testutils.ContextWithNewLogger()
1358 | if err != nil {
1359 | t.Fatalf("unexpected error: %s", err)
1360 | }
1361 | tcs := []struct {
1362 | name string
1363 | in []byte
1364 | wantToolset server.ToolsetConfigs
1365 | }{
1366 | {
1367 | name: "alloydb postgres admin prebuilt tools",
1368 | in: alloydb_admin_config,
1369 | wantToolset: server.ToolsetConfigs{
1370 | "alloydb_postgres_admin_tools": tools.ToolsetConfig{
1371 | Name: "alloydb_postgres_admin_tools",
1372 | ToolNames: []string{"create_cluster", "wait_for_operation", "create_instance", "list_clusters", "list_instances", "list_users", "create_user", "get_cluster", "get_instance", "get_user"},
1373 | },
1374 | },
1375 | },
1376 | {
1377 | name: "cloudsql pg admin prebuilt tools",
1378 | in: cloudsqlpg_admin_config,
1379 | wantToolset: server.ToolsetConfigs{
1380 | "cloud_sql_postgres_admin_tools": tools.ToolsetConfig{
1381 | Name: "cloud_sql_postgres_admin_tools",
1382 | ToolNames: []string{"create_instance", "get_instance", "list_instances", "create_database", "list_databases", "create_user", "wait_for_operation"},
1383 | },
1384 | },
1385 | },
1386 | {
1387 | name: "cloudsql mysql admin prebuilt tools",
1388 | in: cloudsqlmysql_admin_config,
1389 | wantToolset: server.ToolsetConfigs{
1390 | "cloud_sql_mysql_admin_tools": tools.ToolsetConfig{
1391 | Name: "cloud_sql_mysql_admin_tools",
1392 | ToolNames: []string{"create_instance", "get_instance", "list_instances", "create_database", "list_databases", "create_user", "wait_for_operation"},
1393 | },
1394 | },
1395 | },
1396 | {
1397 | name: "cloudsql mssql admin prebuilt tools",
1398 | in: cloudsqlmssql_admin_config,
1399 | wantToolset: server.ToolsetConfigs{
1400 | "cloud_sql_mssql_admin_tools": tools.ToolsetConfig{
1401 | Name: "cloud_sql_mssql_admin_tools",
1402 | ToolNames: []string{"create_instance", "get_instance", "list_instances", "create_database", "list_databases", "create_user", "wait_for_operation"},
1403 | },
1404 | },
1405 | },
1406 | {
1407 | name: "alloydb prebuilt tools",
1408 | in: alloydb_config,
1409 | wantToolset: server.ToolsetConfigs{
1410 | "alloydb_postgres_database_tools": tools.ToolsetConfig{
1411 | Name: "alloydb_postgres_database_tools",
1412 | ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas"},
1413 | },
1414 | },
1415 | },
1416 | {
1417 | name: "bigquery prebuilt tools",
1418 | in: bigquery_config,
1419 | wantToolset: server.ToolsetConfigs{
1420 | "bigquery_database_tools": tools.ToolsetConfig{
1421 | Name: "bigquery_database_tools",
1422 | ToolNames: []string{"analyze_contribution", "ask_data_insights", "execute_sql", "forecast", "get_dataset_info", "get_table_info", "list_dataset_ids", "list_table_ids", "search_catalog"},
1423 | },
1424 | },
1425 | },
1426 | {
1427 | name: "clickhouse prebuilt tools",
1428 | in: clickhouse_config,
1429 | wantToolset: server.ToolsetConfigs{
1430 | "clickhouse_database_tools": tools.ToolsetConfig{
1431 | Name: "clickhouse_database_tools",
1432 | ToolNames: []string{"execute_sql", "list_databases", "list_tables"},
1433 | },
1434 | },
1435 | },
1436 | {
1437 | name: "cloudsqlpg prebuilt tools",
1438 | in: cloudsqlpg_config,
1439 | wantToolset: server.ToolsetConfigs{
1440 | "cloud_sql_postgres_database_tools": tools.ToolsetConfig{
1441 | Name: "cloud_sql_postgres_database_tools",
1442 | ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas"},
1443 | },
1444 | },
1445 | },
1446 | {
1447 | name: "cloudsqlmysql prebuilt tools",
1448 | in: cloudsqlmysql_config,
1449 | wantToolset: server.ToolsetConfigs{
1450 | "cloud_sql_mysql_database_tools": tools.ToolsetConfig{
1451 | Name: "cloud_sql_mysql_database_tools",
1452 | ToolNames: []string{"execute_sql", "list_tables", "get_query_plan", "list_active_queries", "list_tables_missing_unique_indexes", "list_table_fragmentation"},
1453 | },
1454 | },
1455 | },
1456 | {
1457 | name: "cloudsqlmssql prebuilt tools",
1458 | in: cloudsqlmssql_config,
1459 | wantToolset: server.ToolsetConfigs{
1460 | "cloud_sql_mssql_database_tools": tools.ToolsetConfig{
1461 | Name: "cloud_sql_mssql_database_tools",
1462 | ToolNames: []string{"execute_sql", "list_tables"},
1463 | },
1464 | },
1465 | },
1466 | {
1467 | name: "dataplex prebuilt tools",
1468 | in: dataplex_config,
1469 | wantToolset: server.ToolsetConfigs{
1470 | "dataplex_tools": tools.ToolsetConfig{
1471 | Name: "dataplex_tools",
1472 | ToolNames: []string{"search_entries", "lookup_entry", "search_aspect_types"},
1473 | },
1474 | },
1475 | },
1476 | {
1477 | name: "serverless spark prebuilt tools",
1478 | in: serverless_spark_config,
1479 | wantToolset: server.ToolsetConfigs{
1480 | "serverless_spark_tools": tools.ToolsetConfig{
1481 | Name: "serverless_spark_tools",
1482 | ToolNames: []string{"list_batches", "get_batch", "cancel_batch"},
1483 | },
1484 | },
1485 | },
1486 | {
1487 | name: "firestore prebuilt tools",
1488 | in: firestoreconfig,
1489 | wantToolset: server.ToolsetConfigs{
1490 | "firestore_database_tools": tools.ToolsetConfig{
1491 | Name: "firestore_database_tools",
1492 | ToolNames: []string{"get_documents", "add_documents", "update_document", "list_collections", "delete_documents", "query_collection", "get_rules", "validate_rules"},
1493 | },
1494 | },
1495 | },
1496 | {
1497 | name: "mysql prebuilt tools",
1498 | in: mysql_config,
1499 | wantToolset: server.ToolsetConfigs{
1500 | "mysql_database_tools": tools.ToolsetConfig{
1501 | Name: "mysql_database_tools",
1502 | ToolNames: []string{"execute_sql", "list_tables", "get_query_plan", "list_active_queries", "list_tables_missing_unique_indexes", "list_table_fragmentation"},
1503 | },
1504 | },
1505 | },
1506 | {
1507 | name: "mssql prebuilt tools",
1508 | in: mssql_config,
1509 | wantToolset: server.ToolsetConfigs{
1510 | "mssql_database_tools": tools.ToolsetConfig{
1511 | Name: "mssql_database_tools",
1512 | ToolNames: []string{"execute_sql", "list_tables"},
1513 | },
1514 | },
1515 | },
1516 | {
1517 | name: "looker prebuilt tools",
1518 | in: looker_config,
1519 | wantToolset: server.ToolsetConfigs{
1520 | "looker_tools": tools.ToolsetConfig{
1521 | Name: "looker_tools",
1522 | ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look", "get_dashboards", "run_dashboard", "make_dashboard", "add_dashboard_element", "health_pulse", "health_analyze", "health_vacuum", "dev_mode", "get_projects", "get_project_files", "get_project_file", "create_project_file", "update_project_file", "delete_project_file", "get_connections", "get_connection_schemas", "get_connection_databases", "get_connection_tables", "get_connection_table_columns"},
1523 | },
1524 | },
1525 | },
1526 | {
1527 | name: "looker-conversational-analytics prebuilt tools",
1528 | in: lookerca_config,
1529 | wantToolset: server.ToolsetConfigs{
1530 | "looker_conversational_analytics_tools": tools.ToolsetConfig{
1531 | Name: "looker_conversational_analytics_tools",
1532 | ToolNames: []string{"ask_data_insights", "get_models", "get_explores"},
1533 | },
1534 | },
1535 | },
1536 | {
1537 | name: "postgres prebuilt tools",
1538 | in: postgresconfig,
1539 | wantToolset: server.ToolsetConfigs{
1540 | "postgres_database_tools": tools.ToolsetConfig{
1541 | Name: "postgres_database_tools",
1542 | ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas"},
1543 | },
1544 | },
1545 | },
1546 | {
1547 | name: "spanner prebuilt tools",
1548 | in: spanner_config,
1549 | wantToolset: server.ToolsetConfigs{
1550 | "spanner-database-tools": tools.ToolsetConfig{
1551 | Name: "spanner-database-tools",
1552 | ToolNames: []string{"execute_sql", "execute_sql_dql", "list_tables"},
1553 | },
1554 | },
1555 | },
1556 | {
1557 | name: "spanner pg prebuilt tools",
1558 | in: spannerpg_config,
1559 | wantToolset: server.ToolsetConfigs{
1560 | "spanner_postgres_database_tools": tools.ToolsetConfig{
1561 | Name: "spanner_postgres_database_tools",
1562 | ToolNames: []string{"execute_sql", "execute_sql_dql", "list_tables"},
1563 | },
1564 | },
1565 | },
1566 | {
1567 | name: "mindsdb prebuilt tools",
1568 | in: mindsdb_config,
1569 | wantToolset: server.ToolsetConfigs{
1570 | "mindsdb-tools": tools.ToolsetConfig{
1571 | Name: "mindsdb-tools",
1572 | ToolNames: []string{"mindsdb-execute-sql", "mindsdb-sql"},
1573 | },
1574 | },
1575 | },
1576 | {
1577 | name: "sqlite prebuilt tools",
1578 | in: sqlite_config,
1579 | wantToolset: server.ToolsetConfigs{
1580 | "sqlite_database_tools": tools.ToolsetConfig{
1581 | Name: "sqlite_database_tools",
1582 | ToolNames: []string{"execute_sql", "list_tables"},
1583 | },
1584 | },
1585 | },
1586 | {
1587 | name: "neo4j prebuilt tools",
1588 | in: neo4jconfig,
1589 | wantToolset: server.ToolsetConfigs{
1590 | "neo4j_database_tools": tools.ToolsetConfig{
1591 | Name: "neo4j_database_tools",
1592 | ToolNames: []string{"execute_cypher", "get_schema"},
1593 | },
1594 | },
1595 | },
1596 | {
1597 | name: "alloydb postgres observability prebuilt tools",
1598 | in: alloydbobsvconfig,
1599 | wantToolset: server.ToolsetConfigs{
1600 | "alloydb_postgres_cloud_monitoring_tools": tools.ToolsetConfig{
1601 | Name: "alloydb_postgres_cloud_monitoring_tools",
1602 | ToolNames: []string{"get_system_metrics", "get_query_metrics"},
1603 | },
1604 | },
1605 | },
1606 | {
1607 | name: "cloudsql postgres observability prebuilt tools",
1608 | in: cloudsqlpgobsvconfig,
1609 | wantToolset: server.ToolsetConfigs{
1610 | "cloud_sql_postgres_cloud_monitoring_tools": tools.ToolsetConfig{
1611 | Name: "cloud_sql_postgres_cloud_monitoring_tools",
1612 | ToolNames: []string{"get_system_metrics", "get_query_metrics"},
1613 | },
1614 | },
1615 | },
1616 | {
1617 | name: "cloudsql mysql observability prebuilt tools",
1618 | in: cloudsqlmysqlobsvconfig,
1619 | wantToolset: server.ToolsetConfigs{
1620 | "cloud_sql_mysql_cloud_monitoring_tools": tools.ToolsetConfig{
1621 | Name: "cloud_sql_mysql_cloud_monitoring_tools",
1622 | ToolNames: []string{"get_system_metrics", "get_query_metrics"},
1623 | },
1624 | },
1625 | },
1626 | {
1627 | name: "cloudsql mssql observability prebuilt tools",
1628 | in: cloudsqlmssqlobsvconfig,
1629 | wantToolset: server.ToolsetConfigs{
1630 | "cloud_sql_mssql_cloud_monitoring_tools": tools.ToolsetConfig{
1631 | Name: "cloud_sql_mssql_cloud_monitoring_tools",
1632 | ToolNames: []string{"get_system_metrics"},
1633 | },
1634 | },
1635 | },
1636 | {
1637 | name: "cloud healthcare prebuilt tools",
1638 | in: cloudhealthcare_config,
1639 | wantToolset: server.ToolsetConfigs{
1640 | "cloud_healthcare_dataset_tools": tools.ToolsetConfig{
1641 | Name: "cloud_healthcare_dataset_tools",
1642 | ToolNames: []string{"get_dataset", "list_dicom_stores", "list_fhir_stores"},
1643 | },
1644 | "cloud_healthcare_fhir_tools": tools.ToolsetConfig{
1645 | Name: "cloud_healthcare_fhir_tools",
1646 | ToolNames: []string{"get_fhir_store", "get_fhir_store_metrics", "get_fhir_resource", "fhir_patient_search", "fhir_patient_everything", "fhir_fetch_page"},
1647 | },
1648 | "cloud_healthcare_dicom_tools": tools.ToolsetConfig{
1649 | Name: "cloud_healthcare_dicom_tools",
1650 | ToolNames: []string{"get_dicom_store", "get_dicom_store_metrics", "search_dicom_studies", "search_dicom_series", "search_dicom_instances", "retrieve_rendered_dicom_instance"},
1651 | },
1652 | },
1653 | },
1654 | }
1655 |
1656 | for _, tc := range tcs {
1657 | t.Run(tc.name, func(t *testing.T) {
1658 | toolsFile, err := parseToolsFile(ctx, tc.in)
1659 | if err != nil {
1660 | t.Fatalf("failed to parse input: %v", err)
1661 | }
1662 | if diff := cmp.Diff(tc.wantToolset, toolsFile.Toolsets); diff != "" {
1663 | t.Fatalf("incorrect tools parse: diff %v", diff)
1664 | }
1665 | })
1666 | }
1667 | }
1668 |
1669 | func TestMutuallyExclusiveFlags(t *testing.T) {
1670 | testCases := []struct {
1671 | desc string
1672 | args []string
1673 | errString string
1674 | }{
1675 | {
1676 | desc: "--prebuilt and --tools-file",
1677 | args: []string{"--prebuilt", "alloydb", "--tools-file", "my.yaml"},
1678 | errString: "--prebuilt and --tools-file/--tools-files/--tools-folder flags cannot be used simultaneously",
1679 | },
1680 | {
1681 | desc: "--tools-file and --tools-files",
1682 | args: []string{"--tools-file", "my.yaml", "--tools-files", "a.yaml,b.yaml"},
1683 | errString: "--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously",
1684 | },
1685 | {
1686 | desc: "--tools-folder and --tools-files",
1687 | args: []string{"--tools-folder", "./", "--tools-files", "a.yaml,b.yaml"},
1688 | errString: "--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously",
1689 | },
1690 | }
1691 |
1692 | for _, tc := range testCases {
1693 | t.Run(tc.desc, func(t *testing.T) {
1694 | cmd := NewCommand()
1695 | cmd.SetArgs(tc.args)
1696 | err := cmd.Execute()
1697 | if err == nil {
1698 | t.Fatalf("expected an error but got none")
1699 | }
1700 | if !strings.Contains(err.Error(), tc.errString) {
1701 | t.Errorf("expected error message to contain %q, but got %q", tc.errString, err.Error())
1702 | }
1703 | })
1704 | }
1705 | }
1706 |
1707 | func TestFileLoadingErrors(t *testing.T) {
1708 | t.Run("non-existent tools-file", func(t *testing.T) {
1709 | cmd := NewCommand()
1710 | // Use a file that is guaranteed not to exist
1711 | nonExistentFile := filepath.Join(t.TempDir(), "non-existent-tools.yaml")
1712 | cmd.SetArgs([]string{"--tools-file", nonExistentFile})
1713 |
1714 | err := cmd.Execute()
1715 | if err == nil {
1716 | t.Fatal("expected an error for non-existent file but got none")
1717 | }
1718 | if !strings.Contains(err.Error(), "unable to read tool file") {
1719 | t.Errorf("expected error about reading file, but got: %v", err)
1720 | }
1721 | })
1722 |
1723 | t.Run("non-existent tools-folder", func(t *testing.T) {
1724 | cmd := NewCommand()
1725 | nonExistentFolder := filepath.Join(t.TempDir(), "non-existent-folder")
1726 | cmd.SetArgs([]string{"--tools-folder", nonExistentFolder})
1727 |
1728 | err := cmd.Execute()
1729 | if err == nil {
1730 | t.Fatal("expected an error for non-existent folder but got none")
1731 | }
1732 | if !strings.Contains(err.Error(), "unable to access tools folder") {
1733 | t.Errorf("expected error about accessing folder, but got: %v", err)
1734 | }
1735 | })
1736 | }
1737 |
```