diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 6d6216576..df592ab38 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -52,6 +52,10 @@ jobs: name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Ubuntu steps: - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 # for wiremock + with: + java-version: 11 + distribution: 'temurin' - name: Setup go uses: actions/setup-go@v5 with: @@ -63,6 +67,7 @@ jobs: CLOUD_PROVIDER: ${{ matrix.cloud }} GORACE: history_size=7 GO_TEST_PARAMS: ${{ inputs.goTestParams }} + WIREMOCK_PORT: 14335 run: ./ci/test.sh - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 @@ -78,6 +83,10 @@ jobs: name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Mac steps: - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 # for wiremock + with: + java-version: 11 + distribution: 'temurin' - name: Setup go uses: actions/setup-go@v5 with: @@ -88,6 +97,7 @@ jobs: PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} CLOUD_PROVIDER: ${{ matrix.cloud }} GO_TEST_PARAMS: ${{ inputs.goTestParams }} + WIREMOCK_PORT: 14335 run: ./ci/test.sh - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 @@ -103,6 +113,10 @@ jobs: name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Windows steps: - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 # for wiremock + with: + java-version: 11 + distribution: 'temurin' - name: Setup go uses: actions/setup-go@v5 with: @@ -117,6 +131,7 @@ jobs: PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} CLOUD_PROVIDER: ${{ matrix.cloud }} GO_TEST_PARAMS: ${{ inputs.goTestParams }} + WIREMOCK_PORT: 14335 run: ci\\test.bat - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 diff --git a/ci/test.bat b/ci/test.bat index c8343b75a..47956e807 100644 --- a/ci/test.bat +++ b/ci/test.bat @@ -4,6 +4,9 @@ setlocal EnableDelayedExpansion start /b python ci\scripts\hang_webserver.py 12345 +curl -O https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.11.0/wiremock-standalone-3.11.0.jar +START /B java -jar wiremock-standalone-3.11.0.jar --port %WIREMOCK_PORT% + if "%CLOUD_PROVIDER%"=="AWS" set PARAMETER_FILENAME=parameters_aws_golang.json.gpg if "%CLOUD_PROVIDER%"=="AZURE" set PARAMETER_FILENAME=parameters_azure_golang.json.gpg if "%CLOUD_PROVIDER%"=="GCP" set PARAMETER_FILENAME=parameters_gcp_golang.json.gpg diff --git a/ci/test.sh b/ci/test.sh index efbd1de0e..f84e35315 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -7,6 +7,9 @@ set -o pipefail CI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +curl -O https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.11.0/wiremock-standalone-3.11.0.jar +java -jar wiremock-standalone-3.11.0.jar --port $WIREMOCK_PORT & + if [[ -n "$JENKINS_HOME" ]]; then ROOT_DIR="$(cd "${CI_DIR}/.." && pwd)" export WORKSPACE=${WORKSPACE:-/tmp} diff --git a/test_data/wiremock/mappings/select1.json b/test_data/wiremock/mappings/select1.json new file mode 100644 index 000000000..4de4d4c48 --- /dev/null +++ b/test_data/wiremock/mappings/select1.json @@ -0,0 +1,204 @@ +{ + "mappings": [ + { + "scenarioName": "Successful SELECT 1 flow", + "request": { + "urlPathPattern": "/queries/v1/query-request.*", + "method": "POST", + "headers": { + "Authorization": { + "equalTo": "%AUTHORIZATION_HEADER%" + } + } + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "parameters": [ + { + "name": "TIMESTAMP_OUTPUT_FORMAT", + "value": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM" + }, + { + "name": "CLIENT_PREFETCH_THREADS", + "value": 4 + }, + { + "name": "TIME_OUTPUT_FORMAT", + "value": "HH24:MI:SS" + }, + { + "name": "CLIENT_RESULT_CHUNK_SIZE", + "value": 16 + }, + { + "name": "TIMESTAMP_TZ_OUTPUT_FORMAT", + "value": "" + }, + { + "name": "CLIENT_SESSION_KEEP_ALIVE", + "value": false + }, + { + "name": "QUERY_CONTEXT_CACHE_SIZE", + "value": 5 + }, + { + "name": "CLIENT_METADATA_USE_SESSION_DATABASE", + "value": false + }, + { + "name": "CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED", + "value": false + }, + { + "name": "ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1", + "value": true + }, + { + "name": "TIMESTAMP_NTZ_OUTPUT_FORMAT", + "value": "YYYY-MM-DD HH24:MI:SS.FF3" + }, + { + "name": "CLIENT_RESULT_PREFETCH_THREADS", + "value": 1 + }, + { + "name": "CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX", + "value": false + }, + { + "name": "CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ", + "value": true + }, + { + "name": "CLIENT_MEMORY_LIMIT", + "value": 1536 + }, + { + "name": "CLIENT_TIMESTAMP_TYPE_MAPPING", + "value": "TIMESTAMP_LTZ" + }, + { + "name": "TIMEZONE", + "value": "America/Los_Angeles" + }, + { + "name": "SERVICE_NAME", + "value": "" + }, + { + "name": "CLIENT_RESULT_PREFETCH_SLOTS", + "value": 2 + }, + { + "name": "CLIENT_TELEMETRY_ENABLED", + "value": true + }, + { + "name": "CLIENT_DISABLE_INCIDENTS", + "value": true + }, + { + "name": "CLIENT_USE_V1_QUERY_API", + "value": true + }, + { + "name": "CLIENT_RESULT_COLUMN_CASE_INSENSITIVE", + "value": false + }, + { + "name": "CSV_TIMESTAMP_FORMAT", + "value": "" + }, + { + "name": "BINARY_OUTPUT_FORMAT", + "value": "HEX" + }, + { + "name": "CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS", + "value": false + }, + { + "name": "CLIENT_TELEMETRY_SESSIONLESS_ENABLED", + "value": true + }, + { + "name": "DATE_OUTPUT_FORMAT", + "value": "YYYY-MM-DD" + }, + { + "name": "CLIENT_STAGE_ARRAY_BINDING_THRESHOLD", + "value": 65280 + }, + { + "name": "CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY", + "value": 3600 + }, + { + "name": "CLIENT_SESSION_CLONE", + "value": false + }, + { + "name": "AUTOCOMMIT", + "value": true + }, + { + "name": "TIMESTAMP_LTZ_OUTPUT_FORMAT", + "value": "" + } + ], + "rowtype": [ + { + "name": "1", + "database": "", + "schema": "", + "table": "", + "nullable": false, + "length": null, + "type": "fixed", + "scale": 0, + "precision": 1, + "byteLength": null, + "collation": null + } + ], + "rowset": [ + [ + "1" + ] + ], + "total": 1, + "returned": 1, + "queryId": "01ba13b4-0104-e9fd-0000-0111029ca00e", + "databaseProvider": null, + "finalDatabaseName": null, + "finalSchemaName": null, + "finalWarehouseName": "TEST_XSMALL", + "finalRoleName": "ACCOUNTADMIN", + "numberOfBinds": 0, + "arrayBindSupported": false, + "statementTypeId": 4096, + "version": 1, + "sendResultTime": 1738317395581, + "queryResultFormat": "json", + "queryContext": { + "entries": [ + { + "id": 0, + "timestamp": 1738317395574564, + "priority": 0, + "context": "CPbPTg==" + } + ] + } + }, + "code": null, + "message": null, + "success": true + } + } + } + ] +} \ No newline at end of file diff --git a/test_data/wiremock/mappings/telemetry.json b/test_data/wiremock/mappings/telemetry.json new file mode 100644 index 000000000..a61c2b6b0 --- /dev/null +++ b/test_data/wiremock/mappings/telemetry.json @@ -0,0 +1,22 @@ +{ + "mappings": [ + { + "scenarioName": "Successful telemetry flow", + "request": { + "urlPathPattern": "/telemetry/send", + "method": "POST" + }, + "response": { + "status": 200, + "jsonBody": { + "data": { + "code": null, + "data": "Log Received", + "message": null, + "success": true + } + } + } + } + ] +} \ No newline at end of file diff --git a/wiremock_test.go b/wiremock_test.go new file mode 100644 index 000000000..ed37c0bdb --- /dev/null +++ b/wiremock_test.go @@ -0,0 +1,105 @@ +package gosnowflake + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "testing" +) + +var wiremock *wiremockClient = newWiremock() + +type wiremockClient struct { + protocol string + host string + port int + client http.Client +} + +func newWiremock() *wiremockClient { + wmHost := os.Getenv("WIREMOCK_HOST") + if wmHost == "" { + wmHost = "127.0.0.1" + } + wmPortStr := os.Getenv("WIREMOCK_PORT") + if wmPortStr == "" { + wmPortStr = "14355" + } + wmPort, err := strconv.Atoi(wmPortStr) + if err != nil { + panic(fmt.Sprintf("WIREMOCK_PORT is not a number: %v", wmPortStr)) + } + wmProtocol := os.Getenv("WIREMOCK_PROTOCOL") + if wmProtocol == "" { + wmProtocol = "http" + } + return &wiremockClient{ + protocol: wmProtocol, + host: wmHost, + port: wmPort, + } +} + +func (wm *wiremockClient) connectionConfig() *Config { + return &Config{ + User: "testUser", + Host: wm.host, + Port: wm.port, + Account: "testAccount", + Protocol: "http", + } +} + +type wiremockMapping struct { + filePath string + params map[string]string +} + +func (wm *wiremockClient) registerMappings(t *testing.T, mappings ...wiremockMapping) { + for _, mapping := range wm.enrichWithTelemetry(mappings) { + f, err := os.Open("test_data/wiremock/mappings/" + mapping.filePath) + assertNilF(t, err) + defer f.Close() + mappingBodyBytes, err := io.ReadAll(f) + assertNilF(t, err) + mappingBody := string(mappingBodyBytes) + for key, val := range mapping.params { + mappingBody = strings.Replace(mappingBody, key, val, 1) + } + resp, err := wm.client.Post(fmt.Sprintf("%v/import", wm.mappingsURL()), "application/json", strings.NewReader(mappingBody)) + assertNilF(t, err) + if resp.StatusCode != http.StatusOK { + respBody, err := io.ReadAll(resp.Body) + assertNilF(t, err) + t.Fatalf("cannot create mapping.\n%v", string(respBody)) + } + } + t.Cleanup(func() { + req, err := http.NewRequest("DELETE", wm.mappingsURL(), nil) + assertNilE(t, err) + _, err = wm.client.Do(req) + assertNilE(t, err) + }) +} + +func (wm *wiremockClient) enrichWithTelemetry(mappings []wiremockMapping) []wiremockMapping { + return append(mappings, wiremockMapping{ + filePath: "telemetry.json", + }) +} + +func (wm *wiremockClient) mappingsURL() string { + return fmt.Sprintf("%v://%v:%v/__admin/mappings", wm.protocol, wm.host, wm.port) +} + +// just to satisfy not used private variables and functions +// to be removed with first real PR that uses wiremock +func TestWiremock(t *testing.T) { + skipOnJenkins(t, "wiremock is not enabled on Jenkins") + wiremock.registerMappings(t, + wiremockMapping{filePath: "select1.json"}) + wiremock.connectionConfig() +}