Files
SpacetimeDB/modules/sdk-test-procedure-cpp/src/lib.cpp
T
joshua-spacetime fc47257d85 Use SpacetimeDBGuard for SDK test suite (#5340)
# Description of Changes

Use an isolated server process per SDK test instead of a single process
for all of the tests. In addition to reducing the memory footprint of
each test run, this should also allow for more parallelism among the
individual tests.

# API and ABI breaking changes

None

# Expected complexity level and risk

1.5

# Testing

The SDK test suite should continue to work
2026-06-17 00:16:10 +00:00

261 lines
8.4 KiB
C++

#include "spacetimedb.h"
using namespace SpacetimeDB;
// ============================================================================
// Test Types
// ============================================================================
struct ReturnStruct {
uint32_t a;
std::string b;
};
SPACETIMEDB_STRUCT(ReturnStruct, a, b)
SPACETIMEDB_ENUM(ReturnEnum,
(A, uint32_t),
(B, std::string)
)
// Table for transaction tests
struct MyTable {
ReturnStruct field;
};
SPACETIMEDB_STRUCT(MyTable, field)
SPACETIMEDB_TABLE(MyTable, my_table, Public)
// Table for UUID ordering tests
struct PkUuid {
Uuid u;
uint8_t data;
};
SPACETIMEDB_STRUCT(PkUuid, u, data)
SPACETIMEDB_TABLE(PkUuid, pk_uuid, Public)
// Table for scheduled procedure tests
struct ScheduledProcTable {
uint64_t scheduled_id;
ScheduleAt scheduled_at;
Timestamp reducer_ts;
uint8_t x;
uint8_t y;
};
SPACETIMEDB_STRUCT(ScheduledProcTable, scheduled_id, scheduled_at, reducer_ts, x, y)
SPACETIMEDB_TABLE(ScheduledProcTable, scheduled_proc_table, Private)
FIELD_PrimaryKeyAutoInc(scheduled_proc_table, scheduled_id);
SPACETIMEDB_SCHEDULE(scheduled_proc_table, 1, scheduled_proc) // Column 1 is scheduled_at
// Table for storing procedure results
struct ProcInsertsInto {
Timestamp reducer_ts;
Timestamp procedure_ts;
uint8_t x;
uint8_t y;
};
SPACETIMEDB_STRUCT(ProcInsertsInto, reducer_ts, procedure_ts, x, y)
SPACETIMEDB_TABLE(ProcInsertsInto, proc_inserts_into, Public)
// ============================================================================
// Procedure Tests - Part 1: Return Values
// ============================================================================
// Test returning a primitive type
SPACETIMEDB_PROCEDURE(uint32_t, return_primitive, ProcedureContext ctx, uint32_t lhs, uint32_t rhs) {
return lhs + rhs;
}
// Test returning a struct
SPACETIMEDB_PROCEDURE(ReturnStruct, return_struct, ProcedureContext ctx, uint32_t a, std::string b) {
return ReturnStruct{a, b};
}
// Test returning enum variant A
SPACETIMEDB_PROCEDURE(ReturnEnum, return_enum_a, ProcedureContext ctx, uint32_t a) {
return ReturnEnum{a};
}
// Test returning enum variant B
SPACETIMEDB_PROCEDURE(ReturnEnum, return_enum_b, ProcedureContext ctx, std::string b) {
return ReturnEnum{b};
}
// Test procedure that panics
SPACETIMEDB_PROCEDURE(Unit, will_panic, ProcedureContext ctx) {
LOG_PANIC("This procedure is expected to panic");
return Unit{}; // Never reached
}
// ============================================================================
// Procedure Tests - Part 2: Transactions
// ============================================================================
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
// Helper function to insert a row
void insert_my_table(TxContext& tx) {
tx.db[my_table].insert(MyTable{
ReturnStruct{42, "magic"}
});
}
// Helper function to assert row count
void assert_row_count(ProcedureContext& ctx, uint64_t count) {
ctx.with_tx([count](TxContext& tx) {
uint64_t actual = tx.db[my_table].count();
if (actual != count) {
LOG_PANIC("Expected " + std::to_string(count) + " rows but got " + std::to_string(actual));
}
});
}
// Test transaction that commits
SPACETIMEDB_PROCEDURE(Unit, insert_with_tx_commit, ProcedureContext ctx) {
// Insert a row and commit
ctx.with_tx(insert_my_table);
// Assert that there's a row
assert_row_count(ctx, 1);
return Unit{};
}
// Test transaction that rolls back
SPACETIMEDB_PROCEDURE(Unit, insert_with_tx_rollback, ProcedureContext ctx) {
// Use try_with_tx and return false to rollback
ctx.try_with_tx([](TxContext& tx) -> bool {
insert_my_table(tx);
return false; // Rollback
});
// Assert that there's not a row
assert_row_count(ctx, 0);
return Unit{};
}
#endif // SPACETIMEDB_UNSTABLE_FEATURES
// ============================================================================
// Procedure Tests - Part 4: HTTP Requests
// ============================================================================
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
// Test HTTP GET request to the module's own schema endpoint
SPACETIMEDB_PROCEDURE(std::string, read_my_schema, ProcedureContext ctx, std::string server_url) {
// Get the module identity (database address)
Identity module_identity = ctx.database_identity();
std::string identity_hex = module_identity.to_hex_string();
while (!server_url.empty() && server_url.back() == '/') {
server_url.pop_back();
}
LOG_INFO("read_my_schema using identity: " + identity_hex);
// Make HTTP GET request to the schema endpoint (matches Rust)
std::string url = server_url + "/v1/database/" + identity_hex + "/schema?version=9";
auto result = ctx.http.get(url);
if (!result.is_ok()) {
LOG_INFO("read_my_schema error: " + result.error());
LOG_PANIC(result.error());
return ""; // Never reached
}
auto& response = result.value();
std::string body = response.body.to_string_utf8_lossy();
LOG_INFO("read_my_schema status: " + std::to_string(response.status_code) + ", body length: " + std::to_string(body.length()));
return body;
}
// Test HTTP request with invalid URL (should fail gracefully)
SPACETIMEDB_PROCEDURE(std::string, invalid_request, ProcedureContext ctx) {
auto result = ctx.http.get("http://foo.invalid/");
if (result.is_ok()) {
// Unexpected success - panic like Rust version
auto& response = result.value();
std::string body = response.body.to_string_utf8_lossy();
LOG_INFO("invalid_request unexpected success: " + body);
LOG_PANIC("Got result from requesting `http://foo.invalid`... huh?\n" + body);
return ""; // Never reached
}
std::string error = result.error();
LOG_INFO("invalid_request expected error: " + error);
return error;
}
#endif // SPACETIMEDB_UNSTABLE_FEATURES
// ============================================================================
// UUID Tests
// ============================================================================
// Test UUID v7 generation and ordering
SPACETIMEDB_PROCEDURE(Unit, sorted_uuids_insert, ProcedureContext ctx) {
ctx.with_tx([](TxContext& tx) {
// Generate and insert 1000 UUIDs
for (int i = 0; i < 1000; i++) {
Uuid uuid = tx.new_uuid_v7();
tx.db[pk_uuid].insert(PkUuid{uuid, 0});
}
// Verify UUIDs are sorted
std::optional<Uuid> last_uuid;
for (const auto& row : tx.db[pk_uuid]) {
if (last_uuid.has_value()) {
if (last_uuid.value() >= row.u) {
LOG_PANIC("UUIDs are not sorted correctly");
}
}
last_uuid = row.u;
}
LOG_INFO("Successfully inserted and verified 1000 sorted UUIDs");
});
return Unit{};
}
// ============================================================================
// Scheduled Procedure Tests
// ============================================================================
// Reducer that schedules the scheduled_proc procedure
SPACETIMEDB_REDUCER(schedule_proc, ReducerContext ctx) {
LOG_INFO("schedule_proc called at timestamp: " + std::to_string(ctx.timestamp.micros_since_epoch()));
// Schedule the procedure to run in 1s (1000ms = 1,000,000 microseconds)
ctx.db[scheduled_proc_table].insert(ScheduledProcTable{
0, // scheduled_id (auto-incremented)
ScheduleAt(TimeDuration::from_micros(1000000)), // 1 second from now
ctx.timestamp, // Store the timestamp at which this reducer was called
42, // x
24 // y
});
return Ok();
}
// Procedure that should be called 1s after schedule_proc
SPACETIMEDB_PROCEDURE(Unit, scheduled_proc, ProcedureContext ctx, ScheduledProcTable data) {
Timestamp reducer_ts = data.reducer_ts;
uint8_t x = data.x;
uint8_t y = data.y;
Timestamp procedure_ts = ctx.timestamp;
LOG_INFO("scheduled_proc called - procedure_ts: " + std::to_string(procedure_ts.micros_since_epoch()) +
", reducer_ts: " + std::to_string(reducer_ts.micros_since_epoch()));
ctx.with_tx([reducer_ts, procedure_ts, x, y](TxContext& tx) {
tx.db[proc_inserts_into].insert(ProcInsertsInto{
reducer_ts,
procedure_ts,
x,
y
});
});
return Unit{};
}