Skip to content
This repository was archived by the owner on Sep 5, 2025. It is now read-only.

Commit 5799852

Browse files
Add additional tests
1 parent b783f50 commit 5799852

File tree

2 files changed

+282
-17
lines changed

2 files changed

+282
-17
lines changed

client_test.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package modusgraph_test
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"os"
12+
"sync"
13+
"testing"
14+
"time"
15+
16+
mg "github.com/hypermodeinc/modusgraph"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
func TestClientPool(t *testing.T) {
21+
testCases := []struct {
22+
name string
23+
uri string
24+
skip bool
25+
}{
26+
{
27+
name: "ClientPoolWithFileURI",
28+
uri: "file://" + t.TempDir(),
29+
},
30+
{
31+
name: "ClientPoolWithDgraphURI",
32+
uri: "dgraph://" + os.Getenv("MODUSGRAPH_TEST_ADDR"),
33+
skip: os.Getenv("MODUSGRAPH_TEST_ADDR") == "",
34+
},
35+
}
36+
37+
for _, tc := range testCases {
38+
t.Run(tc.name, func(t *testing.T) {
39+
if tc.skip {
40+
t.Skip("Skipping test as MODUSGRAPH_TEST_ADDR is not set")
41+
}
42+
43+
// Create a client with pool size 10
44+
client, err := mg.NewClient(tc.uri, mg.WithPoolSize(10))
45+
require.NoError(t, err)
46+
defer client.Close()
47+
48+
// Test concurrent client pool usage
49+
const numWorkers = 20
50+
var wg sync.WaitGroup
51+
var mu sync.Mutex
52+
var clientCount int
53+
54+
for i := 0; i < numWorkers; i++ {
55+
wg.Add(1)
56+
go func() {
57+
defer wg.Done()
58+
59+
// Get a client from the pool
60+
client, cleanup, err := client.DgraphClient()
61+
require.NoError(t, err)
62+
require.NotNil(t, client)
63+
txn := client.NewReadOnlyTxn()
64+
ctx := context.Background()
65+
_, err = txn.Query(ctx, "query { q(func: uid(1)) { uid } }")
66+
require.NoError(t, err)
67+
err = txn.Discard(ctx)
68+
require.NoError(t, err)
69+
70+
// Verify we got a valid Dgraph client
71+
if client != nil {
72+
mu.Lock()
73+
clientCount++
74+
mu.Unlock()
75+
}
76+
77+
// Clean up the client
78+
cleanup()
79+
}()
80+
}
81+
82+
// Wait for all workers to complete
83+
wg.Wait()
84+
85+
// Verify we got clients from the pool
86+
require.GreaterOrEqual(t, clientCount, 1)
87+
88+
// Get a client before close
89+
beforeClient, cleanupBefore, err := client.DgraphClient()
90+
require.NoError(t, err)
91+
require.NotNil(t, beforeClient)
92+
93+
// Close the client pool
94+
client.Close()
95+
time.Sleep(100 * time.Millisecond) // Give some time for cleanup
96+
97+
// Verify we can still get a new client after close (pool will create a new one)
98+
afterClient, cleanupAfter, err := client.DgraphClient()
99+
require.NoError(t, err)
100+
require.NotNil(t, afterClient)
101+
102+
// Verify the client is actually new
103+
require.NotEqual(t, fmt.Sprintf("%p", beforeClient), fmt.Sprintf("%p", afterClient))
104+
105+
// Clean up the client
106+
cleanupAfter()
107+
108+
// Also clean up the before client if it wasn't already closed
109+
cleanupBefore()
110+
})
111+
}
112+
113+
// Reset singleton at the end of the test to ensure the next test can start fresh
114+
mg.ResetSingleton()
115+
}
116+
117+
func TestClientPoolStress(t *testing.T) {
118+
testCases := []struct {
119+
name string
120+
uri string
121+
skip bool
122+
}{
123+
{
124+
name: "ClientPoolStressWithFileURI",
125+
uri: "file://" + t.TempDir(),
126+
},
127+
{
128+
name: "ClientPoolStressWithDgraphURI",
129+
uri: "dgraph://" + os.Getenv("MODUSGRAPH_TEST_ADDR"),
130+
skip: os.Getenv("MODUSGRAPH_TEST_ADDR") == "",
131+
},
132+
}
133+
134+
for _, tc := range testCases {
135+
t.Run(tc.name, func(t *testing.T) {
136+
if tc.skip {
137+
t.Skip("Skipping test as MODUSGRAPH_TEST_ADDR is not set")
138+
}
139+
140+
// Create a client with pool size 10
141+
client, err := mg.NewClient(tc.uri, mg.WithPoolSize(10))
142+
require.NoError(t, err)
143+
defer func() {
144+
client.Close()
145+
}()
146+
147+
// Test concurrent client pool usage with high load
148+
const numWorkers = 20
149+
const iterations = 10
150+
var wg sync.WaitGroup
151+
var successCount int
152+
var errorCount int
153+
var mu sync.Mutex
154+
155+
for i := 0; i < numWorkers; i++ {
156+
wg.Add(1)
157+
go func() {
158+
defer wg.Done()
159+
for j := 0; j < iterations; j++ {
160+
dgraphClient, cleanup, err := client.DgraphClient()
161+
if err != nil {
162+
mu.Lock()
163+
errorCount++
164+
mu.Unlock()
165+
continue
166+
}
167+
168+
if dgraphClient != nil {
169+
// Test the client works
170+
txn := dgraphClient.NewReadOnlyTxn()
171+
ctx := context.Background()
172+
_, err = txn.Query(ctx, "query { q(func: uid(1)) { uid } }")
173+
if err != nil {
174+
err = txn.Discard(ctx)
175+
if err != nil {
176+
mu.Lock()
177+
errorCount++
178+
mu.Unlock()
179+
}
180+
cleanup()
181+
continue
182+
}
183+
err = txn.Discard(ctx)
184+
if err != nil {
185+
mu.Lock()
186+
errorCount++
187+
mu.Unlock()
188+
cleanup()
189+
continue
190+
}
191+
192+
mu.Lock()
193+
successCount++
194+
mu.Unlock()
195+
}
196+
197+
// Clean up the client
198+
cleanup()
199+
}
200+
}()
201+
}
202+
203+
wg.Wait()
204+
205+
require.Greater(t, successCount, 0)
206+
})
207+
208+
mg.ResetSingleton()
209+
}
210+
}

query_test.go

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
/*
2-
* Copyright 2025 Hypermode Inc. and Contributors
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
2+
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
3+
* SPDX-License-Identifier: Apache-2.0
154
*/
165

176
package modusgraph_test
@@ -73,10 +62,16 @@ func TestClientSimpleGet(t *testing.T) {
7362
}
7463
}
7564

65+
type GeoLocation struct {
66+
Type string `json:"type"`
67+
Coord []float64 `json:"coordinates"`
68+
}
69+
7670
type QueryTestRecord struct {
77-
Name string `json:"name,omitempty" dgraph:"index=exact,term unique"`
78-
Age int `json:"age,omitempty"`
79-
BirthDate time.Time `json:"birthDate,omitzero"`
71+
Name string `json:"name,omitempty" dgraph:"index=exact,term unique"`
72+
Age int `json:"age,omitempty" dgraph:"index=int"`
73+
BirthDate time.Time `json:"birthDate,omitzero"`
74+
Location *GeoLocation `json:"location,omitempty" dgraph:"type=geo index=geo"`
8075

8176
UID string `json:"uid,omitempty"`
8277
DType []string `json:"dgraph.type,omitempty"`
@@ -100,6 +95,18 @@ func TestClientQuery(t *testing.T) {
10095
},
10196
}
10297

98+
locations := [][]float64{
99+
{-122.4194, 37.7749}, // San Francisco, USA
100+
{2.2945, 48.8584}, // Paris (Eiffel Tower), France
101+
{-74.0060, 40.7128}, // New York City, USA
102+
{-0.1276, 51.5072}, // London, UK
103+
{139.7690, 35.6804}, // Tokyo, Japan
104+
{77.2090, 28.6139}, // New Delhi, India
105+
{31.2357, 30.0444}, // Cairo, Egypt
106+
{151.2093, -33.8688}, // Sydney, Australia
107+
{-43.1729, -22.9068}, // Rio de Janeiro, Brazil
108+
{116.4074, 39.9042}, // Beijing, China
109+
}
103110
for _, tc := range testCases {
104111
t.Run(tc.name, func(t *testing.T) {
105112
if tc.skip {
@@ -117,6 +124,10 @@ func TestClientQuery(t *testing.T) {
117124
Name: fmt.Sprintf("Test Entity %d", i),
118125
Age: 30 + i,
119126
BirthDate: birthDate.AddDate(0, 0, i),
127+
Location: &GeoLocation{
128+
Type: "Point",
129+
Coord: locations[i],
130+
},
120131
}
121132
}
122133
ctx := context.Background()
@@ -140,6 +151,7 @@ func TestClientQuery(t *testing.T) {
140151
require.Equal(t, result[i].Name, fmt.Sprintf("Test Entity %d", i), "Name should match")
141152
require.Equal(t, result[i].Age, 30+i, "Age should match")
142153
require.Equal(t, result[i].BirthDate, birthDate.AddDate(0, 0, i), "BirthDate should match")
154+
require.Equal(t, result[i].Location.Coord, locations[i], "Location coordinates should match")
143155
}
144156
})
145157

@@ -156,6 +168,33 @@ func TestClientQuery(t *testing.T) {
156168
}
157169
})
158170

171+
t.Run("QueryWithGeoFilters", func(t *testing.T) {
172+
var result []QueryTestRecord
173+
err := client.Query(ctx, QueryTestRecord{}).
174+
Filter(`(near(location, [2.2946, 48.8585], 1000))`). // just a few meters from the Eiffel Tower
175+
Nodes(&result)
176+
require.NoError(t, err, "Query should succeed")
177+
require.Len(t, result, 1, "Should have 1 entity")
178+
require.Equal(t, result[0].Name, "Test Entity 1", "Name should match")
179+
180+
var rawResult struct {
181+
Data []QueryTestRecord `json:"q"`
182+
}
183+
parisQuery := `query {
184+
q(func: within(location, [[[2.2945, 48.8584], [2.2690, 48.8800], [2.3300, 48.9000],
185+
[2.4100, 48.8800], [2.4150, 48.8300], [2.3650, 48.8150],
186+
[2.3000, 48.8100], [2.2600, 48.8350], [2.2945, 48.8584]]])) {
187+
uid
188+
name
189+
}
190+
}`
191+
resp, err := client.QueryRaw(ctx, parisQuery, nil)
192+
require.NoError(t, err, "Query should succeed")
193+
require.NoError(t, json.Unmarshal(resp, &rawResult), "Failed to unmarshal response")
194+
require.Len(t, rawResult.Data, 1, "Should have 1 entity")
195+
require.Equal(t, rawResult.Data[0].Name, "Test Entity 1", "Name should match")
196+
})
197+
159198
t.Run("QueryWithPagination", func(t *testing.T) {
160199
var result []QueryTestRecord
161200
count, err := client.Query(ctx, QueryTestRecord{}).First(5).NodesAndCount(&result)
@@ -182,7 +221,8 @@ func TestClientQuery(t *testing.T) {
182221
Data []QueryTestRecord `json:"q"`
183222
}
184223
resp, err := client.QueryRaw(ctx,
185-
`query { q(func: type(QueryTestRecord), orderasc: age) { uid name age birthDate }}`)
224+
`query { q(func: type(QueryTestRecord), orderasc: age) { uid name age birthDate }}`,
225+
nil)
186226
require.NoError(t, err, "Query should succeed")
187227
require.NoError(t, json.Unmarshal(resp, &result), "Failed to unmarshal response")
188228
require.Len(t, result.Data, 10, "Should have 10 entities")
@@ -192,6 +232,21 @@ func TestClientQuery(t *testing.T) {
192232
require.Equal(t, result.Data[i].BirthDate, birthDate.AddDate(0, 0, i), "BirthDate should match")
193233
}
194234
})
235+
236+
t.Run("QueryRawWithVars", func(t *testing.T) {
237+
var result struct {
238+
Data []QueryTestRecord `json:"q"`
239+
}
240+
resp, err := client.QueryRaw(ctx,
241+
`query older_than_inclusive($1: int) { q(func: ge(age, $1)) { uid name age }}`,
242+
map[string]string{"$1": "38"})
243+
require.NoError(t, err, "Query should succeed")
244+
require.NoError(t, json.Unmarshal(resp, &result), "Failed to unmarshal response")
245+
require.Len(t, result.Data, 2, "Should have 2 entities")
246+
for i := range 2 {
247+
require.GreaterOrEqual(t, result.Data[i].Age, 38, "Age should be greater than or equal to 38")
248+
}
249+
})
195250
})
196251
}
197252
}

0 commit comments

Comments
 (0)