Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/functional-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,44 @@ jobs:
EOF
echo "settings.json written:" && cat tests/Couchbase.Analytics.FunctionalTests/settings.json

- name: Wait for analytics service to be ready
run: |
echo "Polling analytics service until it accepts queries..."
for i in $(seq 1 60); do
HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -k \
-u "${CBDINO_USER}:${CBDINO_PASS}" \
-H "Content-Type: application/json" \
-d '{"statement": "SELECT 1;"}' \
"${CBDINO_CONNSTR}/api/v1/request" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
echo "Analytics service is ready (attempt $i)"
break
fi
echo "Attempt $i/60: HTTP $HTTP_CODE — waiting 5s..."
sleep 5
done
if [ "$HTTP_CODE" != "200" ]; then
Comment thread
jeffrymorris marked this conversation as resolved.
echo "ERROR: Analytics service did not become ready after 5 minutes"
exit 1
fi

- name: Create test dataverse for scope-level queries
run: |
echo "Creating test dataverse 'testscope' for scope-level functional tests"
HTTP_CODE=$(curl -sS -o /tmp/dataverse-response.json -w "%{http_code}" -k \
-u "${CBDINO_USER}:${CBDINO_PASS}" \
-H "Content-Type: application/json" \
-d '{"statement": "CREATE DATAVERSE testscope IF NOT EXISTS;"}' \
"${CBDINO_CONNSTR}/api/v1/request")
echo "HTTP status: $HTTP_CODE"
cat /tmp/dataverse-response.json
echo ""
if [ "$HTTP_CODE" != "200" ]; then
echo "ERROR: Dataverse creation failed with HTTP $HTTP_CODE"
exit 1
fi
echo "Dataverse created successfully"

- name: Build and Run Functional Tests
run: |
dotnet test tests/Couchbase.Analytics.FunctionalTests/Couchbase.Analytics.FunctionalTests.csproj \
Expand Down
126 changes: 102 additions & 24 deletions tests/Couchbase.Analytics.FunctionalTests/AsyncAnalyticsTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Exceptions;
using Couchbase.AnalyticsClient.FunctionalTests.Fixtures;
Expand Down Expand Up @@ -33,17 +34,7 @@ public async Task Test_AsyncAnalytics_EndToEnd_Cluster()
Assert.NotNull(handle);

// 2. Poll for the query status
QueryStatus? queryStatus = null;
for (var i = 0; i < 20; i++)
{
queryStatus = await handle.FetchStatusAsync(new FetchStatusOptions());
_outputHelper.WriteLine($"Status: {queryStatus}");
if (queryStatus.ResultsReady)
{
break;
}
await Task.Delay(500);
}
var queryStatus = await PollUntilReadyAsync(handle, queryOptions.QueryTimeout ?? TimeSpan.FromSeconds(30));

Assert.NotNull(queryStatus);
Assert.True(queryStatus!.ResultsReady);
Expand All @@ -53,7 +44,7 @@ public async Task Test_AsyncAnalytics_EndToEnd_Cluster()
Assert.NotNull(resultHandle);

// 4. Fetch the results
var results = await resultHandle.FetchResultsAsync(new FetchResultsOptions());
await using var results = await resultHandle.FetchResultsAsync(new FetchResultsOptions());
Assert.NotNull(results);

var count = 0;
Expand Down Expand Up @@ -86,7 +77,8 @@ public async Task Test_AsyncAnalytics_Cancellation_Cluster()
// It's possible the cancel takes a brief moment to process gracefully on the server.
var ex = await Record.ExceptionAsync(async () =>
{
for (var i = 0; i < 20; i++)
var deadline = Stopwatch.StartNew();
while (deadline.Elapsed < queryOptions.QueryTimeout)
{
var queryStatus = await handle.FetchStatusAsync(new FetchStatusOptions());
if (queryStatus.ResultsReady)
Expand All @@ -112,19 +104,11 @@ public async Task Test_AsyncAnalytics_Cancellation_Cluster()
public async Task Test_AsyncAnalytics_DiscardResults_Cluster()
{
var statement = "select i from array_range(1, 5) as i;";
var handle = await _simpleFixture.Cluster.StartQueryAsync(statement, new StartQueryOptions());
var queryOptions = new StartQueryOptions();
var handle = await _simpleFixture.Cluster.StartQueryAsync(statement, queryOptions);

// Poll for the query status
QueryStatus? queryStatus = null;
for (var i = 0; i < 20; i++)
{
queryStatus = await handle.FetchStatusAsync(new FetchStatusOptions());
if (queryStatus.ResultsReady)
{
break;
}
await Task.Delay(500);
}
var queryStatus = await PollUntilReadyAsync(handle, TimeSpan.FromSeconds(30));

Assert.NotNull(queryStatus);
Assert.True(queryStatus!.ResultsReady);
Expand All @@ -144,4 +128,98 @@ await Assert.ThrowsAsync<QueryNotFoundException>(async () =>
await resultHandle.FetchResultsAsync(new FetchResultsOptions());
});
}

[Fact]
public async Task Test_AsyncAnalytics_EndToEnd_Scope()
{
var statement = "select i from array_range(1, 10) as i;";
var queryOptions = new StartQueryOptions()
{
QueryTimeout = TimeSpan.FromSeconds(30)
};

// 1. Start the query via scope
var handle = await _simpleFixture.TestScope.StartQueryAsync(statement, queryOptions);
Assert.NotNull(handle);

// 2. Poll for the query status
var queryStatus = await PollUntilReadyAsync(handle, queryOptions.QueryTimeout ?? TimeSpan.FromSeconds(30));

Assert.NotNull(queryStatus);
Assert.True(queryStatus!.ResultsReady);

// 3. Fetch results
var resultHandle = queryStatus.ResultHandle();
Assert.NotNull(resultHandle);

await using var results = await resultHandle.FetchResultsAsync(new FetchResultsOptions());
Assert.NotNull(results);

var count = 0;
await foreach (var row in results.Rows)
{
count++;
}

Assert.Equal(9, count);
Assert.Equal(9, results.MetaData.Metrics?.ResultCount);
}

[Fact]
public async Task Test_AsyncAnalytics_Metadata_Cluster()
{
var statement = "select i from array_range(1, 100) as i;";
var queryOptions = new StartQueryOptions()
{
QueryTimeout = TimeSpan.FromSeconds(30)
};

var handle = await _simpleFixture.Cluster.StartQueryAsync(statement, queryOptions);
Assert.NotNull(handle);

var queryStatus = await PollUntilReadyAsync(handle, queryOptions.QueryTimeout ?? TimeSpan.FromSeconds(30));

Assert.NotNull(queryStatus);
Assert.True(queryStatus!.ResultsReady);

var resultHandle = queryStatus.ResultHandle();
await using var results = await resultHandle.FetchResultsAsync(new FetchResultsOptions());

// Consume all rows
var count = 0;
await foreach (var row in results.Rows)
{
count++;
}
Comment thread
davidkelly marked this conversation as resolved.

// Verify row count matches metrics
Assert.Equal(99, count);

// Verify metrics
Assert.NotNull(results.MetaData);
Assert.NotNull(results.MetaData.Metrics);
Assert.Equal(99, results.MetaData.Metrics!.ResultCount);
Assert.NotNull(results.MetaData.Metrics.ElapsedTime);
Assert.NotNull(results.MetaData.Metrics.ExecutionTime);
}

/// <summary>
/// Polls the query handle until results are ready or the deadline is reached.
/// </summary>
private async Task<QueryStatus?> PollUntilReadyAsync(QueryHandle handle, TimeSpan timeout)
{
var deadline = Stopwatch.StartNew();
QueryStatus? queryStatus = null;
while (deadline.Elapsed < timeout)
{
queryStatus = await handle.FetchStatusAsync(new FetchStatusOptions());
_outputHelper.WriteLine($"Status: {queryStatus}");
if (queryStatus.ResultsReady)
{
break;
}
await Task.Delay(500);
}
return queryStatus;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ public class FixtureSettings

[JsonPropertyName("ClientKeyPath2")]
public string? ClientKeyPath2 { get; set; }

[JsonPropertyName("TestDatabase")]
public string TestDatabase { get; set; } = "Default";

[JsonPropertyName("TestScope")]
public string TestScope { get; set; } = "testscope";
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public SimpleFixture()

public Credential Credential { get; private set; }

public Scope TestScope => Cluster.Database(FixtureSettings.TestDatabase).Scope(FixtureSettings.TestScope);

public string CapellaCaCert =
"-----BEGIN CERTIFICATE-----\n" +
"MIIFWzCCA0OgAwIBAgIBATANBgkqhkiG9w0BAQsFADA4MTYwNAYDVQQDEy1kaW5v\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task Test_Streaming_Query()
{
var statement = "select i from array_range(1, 100) as i;";

var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
await using var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
new QueryOptions() { Timeout = TimeSpan.FromSeconds(10), AsStreaming = true });

await foreach (var row in result.Rows)
Expand All @@ -63,7 +63,7 @@ public async Task Test_Blocking_Query()
{
var statement = "select i from array_range(1, 100) as i;";

var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
await using var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
new QueryOptions() { Timeout = TimeSpan.FromSeconds(10), AsStreaming = false });

await foreach (var row in result.Rows)
Expand Down Expand Up @@ -96,7 +96,7 @@ public async Task Test_Query_Metadata_And_Metrics()

var statement = "select i from array_range(1, 100) as i;";

var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
await using var result = await _simpleFixture.Cluster.ExecuteQueryAsync(statement,
new QueryOptions() { Timeout = TimeSpan.FromSeconds(10), AsStreaming = false });

Assert.NotNull(result.MetaData);
Expand Down Expand Up @@ -153,4 +153,40 @@ public async Task Test_Cancellation_Works_Blocking()

await Assert.ThrowsAsync<AnalyticsTimeoutException>(async () => await task.ConfigureAwait(false));
}

[Fact]
public async Task Test_Streaming_Query_Scope()
{
var statement = "select i from array_range(1, 10) as i;";

await using var result = await _simpleFixture.TestScope.ExecuteQueryAsync(statement,
new QueryOptions() { Timeout = TimeSpan.FromSeconds(10), AsStreaming = true });

var count = 0;
await foreach (var row in result.Rows)
{
count++;
}

Assert.Equal(9, count);
Assert.Equal(9, result.MetaData.Metrics!.ResultCount);
}

[Fact]
public async Task Test_Blocking_Query_Scope()
{
var statement = "select i from array_range(1, 10) as i;";

await using var result = await _simpleFixture.TestScope.ExecuteQueryAsync(statement,
new QueryOptions() { Timeout = TimeSpan.FromSeconds(10), AsStreaming = false });

var count = 0;
await foreach (var row in result.Rows)
{
count++;
}

Assert.Equal(9, count);
Assert.Equal(9, result.MetaData.Metrics!.ResultCount);
}
}
Loading