Skip to content

Commit f9dea3b

Browse files
committed
Add integration tests for all 11 Bigtable API methods
- Add UrlScope enum to distinguish table-level vs instance-level APIs - Update macro to support custom URL scopes and suffixes - Fix PingAndWarm, PrepareQuery, ExecuteQuery to use instance-level URLs - Add integration test suite covering all API methods: - ReadRows, SampleRowKeys (read operations) - MutateRow, MutateRows (write operations) - CheckAndMutateRow, ReadModifyWriteRow (atomic operations) - PingAndWarm (connection management) - GenerateInitialChangeStreamPartitions, ReadChangeStream (CDC) - PrepareQuery, ExecuteQuery (GoogleSQL) - Add end-to-end write-then-read test All 12 integration tests pass against live Bigtable instance.
1 parent f3caf48 commit f9dea3b

4 files changed

Lines changed: 474 additions & 18 deletions

File tree

.beads/issues.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{"id":"rust-bigtable-30c","title":"Fix doctests for protobuf 3.x API","description":"Why: 3 doctests fail - MutateRow, MutateRows, CheckAndMutateRow use old delete_from_row field syntax instead of mutation oneof. Done when: cargo test -- --ignored passes.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-10T00:10:09.897449+01:00","updated_at":"2025-12-10T00:12:18.386935+01:00","closed_at":"2025-12-10T00:12:18.386935+01:00","close_reason":"Fixed 3 doctests for protobuf 3.x API compatibility"}
22
{"id":"rust-bigtable-3qg","title":"Update protobuf API usage (RepeatedField -\u003e Vec, getter changes)","description":"Why: protobuf 3.x removed RepeatedField, changed getter prefixes. Done when: all src files use Vec and new getter names.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T23:19:20.609494+01:00","updated_at":"2025-12-09T23:25:32.573329+01:00","closed_at":"2025-12-09T23:25:32.573329+01:00","close_reason":"Updated protobuf API: RepeatedField-\u003eVec, MessageFull trait, direct field access","dependencies":[{"issue_id":"rust-bigtable-3qg","depends_on_id":"rust-bigtable-nko","type":"blocks","created_at":"2025-12-09T23:20:08.478991+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-3qg","depends_on_id":"rust-bigtable-k8t","type":"blocks","created_at":"2025-12-09T23:20:16.756889+01:00","created_by":"daemon"}]}
33
{"id":"rust-bigtable-6u0","title":"Update goauth API (get_token_with_creds -\u003e get_token_blocking)","description":"Why: goauth 0.17 renamed token retrieval function. Done when: utils.rs uses get_token_blocking or async get_token.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T23:19:33.88726+01:00","updated_at":"2025-12-09T23:23:20.639651+01:00","closed_at":"2025-12-09T23:23:20.639651+01:00","close_reason":"Updated get_token_with_creds to get_token_blocking","dependencies":[{"issue_id":"rust-bigtable-6u0","depends_on_id":"rust-bigtable-nko","type":"blocks","created_at":"2025-12-09T23:20:08.546442+01:00","created_by":"daemon"}]}
4+
{"id":"rust-bigtable-8pr","title":"Add integration tests for all Bigtable API methods","description":"Why: No real API testing exists. Done when: All 11 methods have integration tests that run against real Bigtable instance.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-10T00:14:27.318935+01:00","updated_at":"2025-12-10T00:18:55.62926+01:00","closed_at":"2025-12-10T00:18:55.62926+01:00","close_reason":"Added integration tests for all 11 API methods, all passing"}
45
{"id":"rust-bigtable-i1x","title":"Replace rustc-serialize with base64 crate","description":"Why: rustc-serialize is deprecated. Done when: base64 encoding uses base64 crate, rustc-serialize removed from deps.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T23:19:10.820357+01:00","updated_at":"2025-12-09T23:23:20.610896+01:00","closed_at":"2025-12-09T23:23:20.610896+01:00","close_reason":"Replaced rustc_serialize::base64 with base64 crate","dependencies":[{"issue_id":"rust-bigtable-i1x","depends_on_id":"rust-bigtable-nko","type":"blocks","created_at":"2025-12-09T23:20:08.445798+01:00","created_by":"daemon"}]}
56
{"id":"rust-bigtable-k8t","title":"Update build.rs for protobuf-codegen 3.x","description":"Why: protobuf-codegen-pure merged into protobuf-codegen with new API. Done when: build.rs uses Codegen::new() API and compiles.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T23:19:03.487853+01:00","updated_at":"2025-12-09T23:22:51.046635+01:00","closed_at":"2025-12-09T23:22:51.046635+01:00","close_reason":"Updated build.rs to use protobuf-codegen 3.x API, created src/protos.rs to include generated files","dependencies":[{"issue_id":"rust-bigtable-k8t","depends_on_id":"rust-bigtable-nko","type":"blocks","created_at":"2025-12-09T23:20:08.412131+01:00","created_by":"daemon"}]}
67
{"id":"rust-bigtable-mku","title":"Build and fix all compilation errors","description":"Why: After dependency updates, need to verify everything compiles. Done when: cargo build succeeds with no errors.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T23:19:39.538965+01:00","updated_at":"2025-12-09T23:43:43.489442+01:00","closed_at":"2025-12-09T23:43:43.489442+01:00","close_reason":"Build successful, all compilation errors fixed","dependencies":[{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-nko","type":"blocks","created_at":"2025-12-09T23:19:59.452521+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-k8t","type":"blocks","created_at":"2025-12-09T23:19:59.485973+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-i1x","type":"blocks","created_at":"2025-12-09T23:19:59.517895+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-3qg","type":"blocks","created_at":"2025-12-09T23:19:59.549902+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-stv","type":"blocks","created_at":"2025-12-09T23:19:59.584347+01:00","created_by":"daemon"},{"issue_id":"rust-bigtable-mku","depends_on_id":"rust-bigtable-6u0","type":"blocks","created_at":"2025-12-09T23:19:59.616617+01:00","created_by":"daemon"}]}

src/method.rs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ use crate::protos::bigtable::*;
22
use protobuf::MessageFull;
33

44
// AIDEV-NOTE: protobuf 3.x requires MessageFull for JSON serialization via protobuf-json-mapping
5+
// AIDEV-NOTE: UrlScope distinguishes table-level vs instance-level API methods
6+
#[derive(Clone, Copy, Debug, PartialEq)]
7+
pub enum UrlScope {
8+
/// URL format: /projects/{project}/instances/{instance}/tables/{table}:{method}
9+
Table,
10+
/// URL format: /projects/{project}/instances/{instance}:{method}
11+
Instance,
12+
}
13+
514
pub trait BigTable {
615
type M: MessageFull;
716

@@ -10,14 +19,42 @@ pub trait BigTable {
1019
fn set_payload(&mut self, payload: Self::M);
1120
fn url_method(&self) -> &str;
1221
fn is_post(&self) -> bool;
22+
/// Returns the URL scope for this method (default: Table)
23+
fn url_scope(&self) -> UrlScope {
24+
UrlScope::Table
25+
}
1326
}
1427

1528
macro_rules! method {
29+
// Table-level method (default) - auto-generates URL suffix from name
1630
($name: ident, $proto: ty, $post: expr) => {
31+
method!(@impl $name, $proto, $post, UrlScope::Table, {
32+
let mut x = stringify!($name).chars();
33+
let first = x.next().unwrap().to_lowercase().next().unwrap();
34+
let rest = x.as_str();
35+
format!(":{}{}", first, rest)
36+
});
37+
};
38+
// Method with explicit scope - auto-generates URL suffix from name
39+
($name: ident, $proto: ty, $post: expr, $scope: expr) => {
40+
method!(@impl $name, $proto, $post, $scope, {
41+
let mut x = stringify!($name).chars();
42+
let first = x.next().unwrap().to_lowercase().next().unwrap();
43+
let rest = x.as_str();
44+
format!(":{}{}", first, rest)
45+
});
46+
};
47+
// Method with explicit scope and custom URL suffix
48+
($name: ident, $proto: ty, $post: expr, $scope: expr, $url_suffix: expr) => {
49+
method!(@impl $name, $proto, $post, $scope, { String::from($url_suffix) });
50+
};
51+
// Internal implementation
52+
(@impl $name: ident, $proto: ty, $post: expr, $scope: expr, $url_method_expr: block) => {
1753
pub struct $name {
1854
pub payload: $proto,
1955
pub url_method: String,
2056
pub is_post: bool,
57+
pub scope: UrlScope,
2158
}
2259

2360
impl $name {
@@ -28,15 +65,11 @@ macro_rules! method {
2865

2966
impl Default for $name {
3067
fn default() -> Self {
31-
// URL suffix from ident
32-
let mut x = stringify!($name).chars();
33-
let first = x.next().unwrap().to_lowercase().next().unwrap();
34-
let rest = x.as_str();
35-
3668
$name {
3769
payload: Default::default(),
38-
url_method: format!(":{}{}", first, rest),
70+
url_method: $url_method_expr,
3971
is_post: $post,
72+
scope: $scope,
4073
}
4174
}
4275
}
@@ -63,6 +96,10 @@ macro_rules! method {
6396
fn is_post(&self) -> bool {
6497
self.is_post
6598
}
99+
100+
fn url_scope(&self) -> UrlScope {
101+
self.scope
102+
}
66103
}
67104
};
68105
}
@@ -280,7 +317,8 @@ method!(ReadModifyWriteRow, ReadModifyWriteRowRequest, true);
280317
/// }
281318
/// ```
282319
fn ping_and_warm_doctest() {}
283-
method!(PingAndWarm, PingAndWarmRequest, true);
320+
// AIDEV-NOTE: PingAndWarm uses instance-level URL with custom suffix ":ping"
321+
method!(PingAndWarm, PingAndWarmRequest, true, UrlScope::Instance, ":ping");
284322

285323
/// ### `GenerateInitialChangeStreamPartitions`
286324
///
@@ -354,7 +392,8 @@ method!(ReadChangeStream, ReadChangeStreamRequest, true);
354392
/// }
355393
/// ```
356394
fn prepare_query_doctest() {}
357-
method!(PrepareQuery, PrepareQueryRequest, true);
395+
// AIDEV-NOTE: PrepareQuery uses instance-level URL
396+
method!(PrepareQuery, PrepareQueryRequest, true, UrlScope::Instance);
358397

359398
/// ### `ExecuteQuery`
360399
///
@@ -379,4 +418,5 @@ method!(PrepareQuery, PrepareQueryRequest, true);
379418
/// }
380419
/// ```
381420
fn execute_query_doctest() {}
382-
method!(ExecuteQuery, ExecuteQueryRequest, true);
421+
// AIDEV-NOTE: ExecuteQuery uses instance-level URL
422+
method!(ExecuteQuery, ExecuteQueryRequest, true, UrlScope::Instance);

src/request.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use curl::easy::{Easy, List};
22
use crate::error::BTErr;
33
use goauth::auth::Token;
4-
use crate::method::{BigTable, ReadRows};
4+
use crate::method::{BigTable, ReadRows, UrlScope};
55
use protobuf_json_mapping;
66
use serde_json;
77
use serde_json::Value;
@@ -26,19 +26,29 @@ impl<'a> Default for BTRequest<'a, ReadRows> {
2626
}
2727

2828
impl<'a, T: BigTable> BTRequest<'a, T> {
29+
// AIDEV-NOTE: form_url handles both table-level and instance-level API methods
2930
pub fn form_url(&self) -> Result<String, BTErr> {
3031
let base = match self.base {
3132
Some(x) => x,
3233
None => "https://bigtable.googleapis.com/v2",
3334
};
34-
Ok(format!(
35-
"{}/projects/{}/instances/{}/tables/{}{}",
36-
base,
37-
self.table.instance.project.name,
38-
self.table.instance.name,
39-
self.table.name,
40-
self.method.url_method()
41-
))
35+
match self.method.url_scope() {
36+
UrlScope::Table => Ok(format!(
37+
"{}/projects/{}/instances/{}/tables/{}{}",
38+
base,
39+
self.table.instance.project.name,
40+
self.table.instance.name,
41+
self.table.name,
42+
self.method.url_method()
43+
)),
44+
UrlScope::Instance => Ok(format!(
45+
"{}/projects/{}/instances/{}{}",
46+
base,
47+
self.table.instance.project.name,
48+
self.table.instance.name,
49+
self.method.url_method()
50+
)),
51+
}
4252
}
4353

4454
pub fn execute(&self, token: &Token) -> Result<Value, BTErr> {

0 commit comments

Comments
 (0)