|  | /* | 
|  | Licensed to the Apache Software Foundation (ASF) under one | 
|  | or more contributor license agreements.  See the NOTICE file | 
|  | distributed with this work for additional information | 
|  | regarding copyright ownership.  The ASF licenses this file | 
|  | to you under the Apache License, Version 2.0 (the | 
|  | "License"); you may not use this file except in compliance | 
|  | with the License.  You may obtain a copy of the License at | 
|  |  | 
|  | http://www.apache.org/licenses/LICENSE-2.0 | 
|  |  | 
|  | Unless required by applicable law or agreed to in writing, | 
|  | software distributed under the License is distributed on an | 
|  | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | KIND, either express or implied.  See the License for the | 
|  | specific language governing permissions and limitations | 
|  | under the License. | 
|  | */ | 
|  |  | 
|  | package gremlingo | 
|  |  | 
|  | import ( | 
|  | "crypto/tls" | 
|  | "fmt" | 
|  | "log" | 
|  | "os" | 
|  | "testing" | 
|  |  | 
|  | "github.com/google/uuid" | 
|  | "github.com/stretchr/testify/assert" | 
|  | "gopkg.in/yaml.v3" | 
|  | ) | 
|  |  | 
|  | func TestClient(t *testing.T) { | 
|  | // Integration test variables. | 
|  | testNoAuthUrl := getEnvOrDefaultString("GREMLIN_SERVER_URL", noAuthUrl) | 
|  | testNoAuthEnable := getEnvOrDefaultBool("RUN_INTEGRATION_TESTS", true) | 
|  | testNoAuthAuthInfo := &AuthInfo{} | 
|  | testNoAuthTlsConfig := &tls.Config{} | 
|  |  | 
|  | t.Run("Test client.SubmitWithOptions()", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testNoAuthUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.TlsConfig = testNoAuthTlsConfig | 
|  | settings.AuthInfo = testNoAuthAuthInfo | 
|  | }) | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.SubmitWithOptions("g.V().count()", *new(RequestOptions)) | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err := resultSet.One() | 
|  | assert.NoError(t, err) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, result) | 
|  | }) | 
|  |  | 
|  | t.Run("Test client.Close()", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testNoAuthUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.TlsConfig = testNoAuthTlsConfig | 
|  | settings.AuthInfo = testNoAuthAuthInfo | 
|  | }) | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, client) | 
|  | resultSet, err := client.Submit("2+2") | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  |  | 
|  | client.Close() | 
|  | pool := client.connections.(*loadBalancingPool) | 
|  | assert.Equal(t, 1, len(pool.connections)) | 
|  | assert.True(t, pool.isClosed) | 
|  | assert.Equal(t, closed, pool.connections[0].state) | 
|  | }) | 
|  |  | 
|  | t.Run("Test client.Submit()", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testNoAuthUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.TlsConfig = testNoAuthTlsConfig | 
|  | settings.AuthInfo = testNoAuthAuthInfo | 
|  | settings.TraversalSource = testServerModernGraphAlias | 
|  | }) | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.Submit("g.V(1)") | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  |  | 
|  | result, ok, err := resultSet.One() | 
|  | assert.NoError(t, err) | 
|  | assert.True(t, ok) | 
|  |  | 
|  | AssertMarkoVertexWithProperties(t, result) | 
|  | }) | 
|  |  | 
|  | t.Run("Test client.submit() with materializeProperties", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testNoAuthUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.TlsConfig = testNoAuthTlsConfig | 
|  | settings.AuthInfo = testNoAuthAuthInfo | 
|  | settings.TraversalSource = testServerModernGraphAlias | 
|  | }) | 
|  |  | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.Submit("g.with('materializeProperties', 'tokens').V(1)") | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err := resultSet.One() | 
|  | assert.NoError(t, err) | 
|  | assert.True(t, ok) | 
|  |  | 
|  | AssertMarkoVertexWithoutProperties(t, result) | 
|  | }) | 
|  |  | 
|  | t.Run("Test deserialization of VertexProperty with properties", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testNoAuthUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.TlsConfig = testNoAuthTlsConfig | 
|  | settings.AuthInfo = testNoAuthAuthInfo | 
|  | settings.TraversalSource = testServerCrewGraphAlias | 
|  | }) | 
|  |  | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.Submit("g.V(7)") | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err := resultSet.One() | 
|  | assert.NoError(t, err) | 
|  | assert.True(t, ok) | 
|  |  | 
|  | AssertVertexPropertiesWithProperties(t, result) | 
|  | }) | 
|  | } | 
|  |  | 
|  | func AssertVertexPropertiesWithProperties(t *testing.T, result *Result) { | 
|  | assert.NotNil(t, result) | 
|  |  | 
|  | vertex, err := result.GetVertex() | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, vertex) | 
|  |  | 
|  | properties, ok := vertex.Properties.([]interface{}) | 
|  | assert.True(t, ok) | 
|  | assert.Equal(t, 4, len(properties)) | 
|  |  | 
|  | property, ok := properties[1].(*VertexProperty) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, property) | 
|  | assert.Equal(t, "centreville", property.Value) | 
|  | vertexPropertyProperties := property.Properties.([]interface{}) | 
|  | assert.Equal(t, 2, len(vertexPropertyProperties)) | 
|  | assert.Equal(t, "startTime", (vertexPropertyProperties[0].(*Property)).Key) | 
|  | assert.Equal(t, int32(1990), (vertexPropertyProperties[0].(*Property)).Value) | 
|  | assert.Equal(t, "endTime", (vertexPropertyProperties[1].(*Property)).Key) | 
|  | assert.Equal(t, int32(2000), (vertexPropertyProperties[1].(*Property)).Value) | 
|  | } | 
|  |  | 
|  | func AssertMarkoVertexWithProperties(t *testing.T, result *Result) { | 
|  | assert.NotNil(t, result) | 
|  |  | 
|  | vertex, err := result.GetVertex() | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, vertex) | 
|  |  | 
|  | properties, ok := vertex.Properties.([]interface{}) | 
|  | assert.True(t, ok) | 
|  | assert.Equal(t, 2, len(properties)) | 
|  |  | 
|  | property, ok := properties[0].(*VertexProperty) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, property) | 
|  | assert.Equal(t, "name", property.Label) | 
|  | assert.Equal(t, "marko", property.Value) | 
|  |  | 
|  | property, ok = properties[1].(*VertexProperty) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, property) | 
|  | assert.Equal(t, "age", property.Label) | 
|  | assert.Equal(t, int32(29), property.Value) | 
|  | } | 
|  |  | 
|  | func AssertMarkoVertexWithoutProperties(t *testing.T, result *Result) { | 
|  | assert.NotNil(t, result) | 
|  |  | 
|  | vertex, err := result.GetVertex() | 
|  | assert.NoError(t, err) | 
|  | assert.NotNil(t, vertex) | 
|  |  | 
|  | properties, ok := vertex.Properties.([]interface{}) | 
|  | assert.True(t, ok) | 
|  | assert.Equal(t, 0, len(properties)) | 
|  | } | 
|  |  | 
|  | // Client is used to connect and interact with a Gremlin-supported server. | 
|  | type SocketServerSettings struct { | 
|  | PORT int `yaml:"PORT"` | 
|  | /** | 
|  | * Configures which serializer will be used. Ex: "GraphBinaryV1" or "GraphSONV2" | 
|  | */ | 
|  | SERIALIZER string `yaml:"SERIALIZER"` | 
|  | /** | 
|  | * If a request with this ID comes to the server, the server responds back with a single vertex picked from Modern | 
|  | * graph. | 
|  | */ | 
|  | SINGLE_VERTEX_REQUEST_ID uuid.UUID `yaml:"SINGLE_VERTEX_REQUEST_ID"` | 
|  | /** | 
|  | * If a request with this ID comes to the server, the server responds back with a single vertex picked from Modern | 
|  | * graph. After a 2 second delay, server sends a Close WebSocket frame on the same connection. | 
|  | */ | 
|  | SINGLE_VERTEX_DELAYED_CLOSE_CONNECTION_REQUEST_ID uuid.UUID `yaml:"SINGLE_VERTEX_DELAYED_CLOSE_CONNECTION_REQUEST_ID"` | 
|  | /** | 
|  | * Server waits for 1 second, then responds with a 500 error status code | 
|  | */ | 
|  | FAILED_AFTER_DELAY_REQUEST_ID uuid.UUID `yaml:"FAILED_AFTER_DELAY_REQUEST_ID"` | 
|  | /** | 
|  | * Server waits for 1 second then responds with a close web socket frame | 
|  | */ | 
|  | CLOSE_CONNECTION_REQUEST_ID uuid.UUID `yaml:"CLOSE_CONNECTION_REQUEST_ID"` | 
|  | /** | 
|  | * Same as CLOSE_CONNECTION_REQUEST_ID | 
|  | */ | 
|  | CLOSE_CONNECTION_REQUEST_ID_2 uuid.UUID `yaml:"CLOSE_CONNECTION_REQUEST_ID_2"` | 
|  | /** | 
|  | * If a request with this ID comes to the server, the server responds with the user agent (if any) that was captured | 
|  | * during the web socket handshake. | 
|  | */ | 
|  | USER_AGENT_REQUEST_ID uuid.UUID `yaml:"USER_AGENT_REQUEST_ID"` | 
|  | /** | 
|  | * If a request with this ID comes to the server, the server responds with a string containing all overridden | 
|  | * per request settings from the request message. String will be of the form | 
|  | * "requestId=19436d9e-f8fc-4b67-8a76-deec60918424 evaluationTimeout=1234, batchSize=12, userAgent=testUserAgent" | 
|  | */ | 
|  | PER_REQUEST_SETTINGS_REQUEST_ID uuid.UUID `yaml:"PER_REQUEST_SETTINGS_REQUEST_ID"` | 
|  | } | 
|  |  | 
|  | func FromYaml(path string) *SocketServerSettings { | 
|  | socketServerSettings := new(SocketServerSettings) | 
|  | f, err := os.ReadFile(path) | 
|  | if err != nil { | 
|  | log.Fatal(err) | 
|  | } | 
|  | if err := yaml.Unmarshal(f, socketServerSettings); err != nil { | 
|  | log.Fatal(err) | 
|  | } | 
|  | return socketServerSettings | 
|  | } | 
|  |  | 
|  | func TestClientAgainstSocketServer(t *testing.T) { | 
|  | // Integration test variables. | 
|  | testNoAuthEnable := getEnvOrDefaultBool("RUN_INTEGRATION_TESTS", true) | 
|  | settings := FromYaml(getEnvOrDefaultString("GREMLIN_SOCKET_SERVER_CONFIG_PATH", "../../gremlin-tools/gremlin-socket-server/conf/test-ws-gremlin.yaml")) | 
|  | testSocketServerUrl := getEnvOrDefaultString("GREMLIN_SOCKET_SERVER_URL", "ws://localhost") | 
|  | testSocketServerUrl = fmt.Sprintf("%s:%v/gremlin", testSocketServerUrl, settings.PORT) | 
|  |  | 
|  | /** | 
|  | * Note: This test does not demonstrate anything useful other than the ability to connect to and | 
|  | * use gremlin-socket-server. Currently, implementing more useful tests are blocked by TINKERPOP-2845. | 
|  | * This test can be safely removed once more interesting tests have been added which utilize | 
|  | * gremlin-socket-server. | 
|  | */ | 
|  | t.Run("Should get single vertex response from gremlin socket server", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testSocketServerUrl) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  | resultSet, err := client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.SINGLE_VERTEX_REQUEST_ID).Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err := resultSet.One() | 
|  | assert.Nil(t, err) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, result) | 
|  | }) | 
|  |  | 
|  | /** | 
|  | * Tests that client is correctly sending user agent during web socket handshake by having the server return | 
|  | * the captured user agent. | 
|  | */ | 
|  | t.Run("Should include user agent in handshake request", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testSocketServerUrl) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.USER_AGENT_REQUEST_ID).Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  |  | 
|  | result, ok, err := resultSet.One() | 
|  | assert.Nil(t, err) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, result) | 
|  |  | 
|  | userAgentResponse := result.GetString() | 
|  | assert.Equal(t, userAgent, userAgentResponse) | 
|  | }) | 
|  |  | 
|  | /** | 
|  | * Tests that no user agent (other than the default one provided by gorilla) is sent to server when | 
|  | * that behaviour is disabled. | 
|  | */ | 
|  | t.Run("Should not include user agent in handshake request if disabled", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testSocketServerUrl, | 
|  | func(settings *ClientSettings) { | 
|  | settings.EnableUserAgentOnConnect = false | 
|  | }) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.USER_AGENT_REQUEST_ID).Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  |  | 
|  | result, ok, err := resultSet.One() | 
|  | assert.Nil(t, err) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, result) | 
|  |  | 
|  | userAgentResponse := result.GetString() | 
|  | //If the gremlin user agent is disabled, the underlying web socket library reverts to sending its default user agent | 
|  | //during connection requests. | 
|  | assert.Contains(t, userAgentResponse, "Go-http-client/") | 
|  | }) | 
|  |  | 
|  | /** | 
|  | * Tests that client is correctly sending all overridable per request settings (requestId, batchSize, | 
|  | * evaluationTimeout, and userAgent) to the server. | 
|  | */ | 
|  | t.Run("Should Send Per Request Settings To Server", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testSocketServerUrl) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, client) | 
|  | defer client.Close() | 
|  |  | 
|  | resultSet, err := client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.PER_REQUEST_SETTINGS_REQUEST_ID). | 
|  | SetEvaluationTimeout(1234). | 
|  | SetBatchSize(12). | 
|  | SetUserAgent("helloWorld"). | 
|  | SetMaterializeProperties("tokens"). | 
|  | Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err := resultSet.One() | 
|  | assert.Nil(t, err) | 
|  | assert.True(t, ok) | 
|  | expectedResult := fmt.Sprintf("requestId=%v evaluationTimeout=%v, batchSize=%v, userAgent=%v, materializeProperties=%v", | 
|  | settings.PER_REQUEST_SETTINGS_REQUEST_ID, 1234, 12, "helloWorld", "tokens") | 
|  | assert.Equal(t, expectedResult, result.Data) | 
|  | }) | 
|  |  | 
|  | /** | 
|  | * Note: This test currently fails due to race condition check in go test and is only included for demonstration | 
|  | * purposes. See https://issues.apache.org/jira/browse/TINKERPOP-2845. | 
|  | * This test should be uncommented with the resolution of TINKERPOP-2845 | 
|  | */ | 
|  | /* | 
|  | t.Run("Should try create new connection if closed by server", func(t *testing.T) { | 
|  | skipTestsIfNotEnabled(t, integrationTestSuiteName, testNoAuthEnable) | 
|  | client, err := NewClient(testSocketServerUrl) | 
|  | defer client.Close() | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, client) | 
|  | resultSet, err := client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.CLOSE_CONNECTION_REQUEST_ID).Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  |  | 
|  | result, ok, err := resultSet.One() | 
|  |  | 
|  | assert.EqualError(t, err, "websocket: close 1005 (no status)") | 
|  |  | 
|  | resultSet, err = client.SubmitWithOptions("1", new(RequestOptionsBuilder). | 
|  | SetRequestId(settings.SINGLE_VERTEX_REQUEST_ID).Create()) | 
|  | assert.Nil(t, err) | 
|  | assert.NotNil(t, resultSet) | 
|  | result, ok, err = resultSet.One() | 
|  | assert.Nil(t, err) | 
|  | assert.True(t, ok) | 
|  | assert.NotNil(t, result) | 
|  | }) | 
|  | */ | 
|  | } |