From 349aed9cbb262d39ceddb160da9fa0025a02a2fa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Apr 2026 02:01:31 +0000 Subject: [PATCH 1/2] fix: add tests for time dimension visibility with compareDateRange and filter-only TDs Adds regression tests for the issue where the GraphQL schema fix (c95317be) introduced validate_query_members_in_annotation() which checked ALL time dimensions against the annotation map, even filter-only ones (no granularity). Filter-only TDs don't produce result columns and aren't in the annotation, so checking them incorrectly triggered 'You requested hidden member' errors. The fix in 98e3c19e correctly skips TDs without granularity in the validation. Tests added: - Rust: compareDateRange empty dataset with filter-only TD (no granularity, dimension not in annotation) - validates the fix works - smoke-rbac: REST API tests for filter-only TD and compareDateRange queries on open cubes with both admin and default users Co-authored-by: Pavel Tiunov --- .../cubejs-testing/test/smoke-rbac.test.ts | 56 +++++++++++++++++++ .../src/query_result_transform.rs | 56 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/packages/cubejs-testing/test/smoke-rbac.test.ts b/packages/cubejs-testing/test/smoke-rbac.test.ts index 22b584c2be4b7..3133e585bf0af 100644 --- a/packages/cubejs-testing/test/smoke-rbac.test.ts +++ b/packages/cubejs-testing/test/smoke-rbac.test.ts @@ -838,6 +838,35 @@ describe('Cube RBAC Engine', () => { expect(result.rawData()).toMatchSnapshot('line_items_view_no_policy_rest'); }); + test('filter-only time dimension should not trigger hidden member error', async () => { + // Regression test: compareDateRange queries decompose into sub-queries + // where each time dimension has only dateRange (no granularity). + // These filter-only TDs don't appear in the annotation map. + // The Rust validate_query_members_in_annotation must skip them. + const result = await client.load({ + measures: ['orders_open.count'], + timeDimensions: [{ + dimension: 'orders_open.created_at', + dateRange: ['2099-01-01', '2099-12-31'], + }], + }); + expect(result.rawData()).toEqual([]); + }); + + test('compareDateRange with filter-only time dimension should work', async () => { + const result = await client.load({ + measures: ['orders_open.count'], + timeDimensions: [{ + dimension: 'orders_open.created_at', + compareDateRange: [ + ['2020-01-01', '2020-06-30'], + ['2020-07-01', '2020-12-31'], + ], + }], + }, { queryType: 'multi' } as any); + expect(result).toBeDefined(); + }); + test('orders_view and cube with default policy', async () => { let error = ''; try { @@ -874,6 +903,33 @@ describe('Cube RBAC Engine', () => { // order_open should return all values since it has no access policy expect(result.rawData()).toMatchSnapshot('orders_open_rest'); }); + + test('filter-only time dimension on open cube should not fail for default user', async () => { + // Even with no roles, querying an open cube with a filter-only + // time dimension (no granularity) should work without hidden member errors. + const result = await defaultClient.load({ + measures: ['orders_open.count'], + timeDimensions: [{ + dimension: 'orders_open.created_at', + dateRange: ['2099-01-01', '2099-12-31'], + }], + }); + expect(result.rawData()).toEqual([]); + }); + + test('compareDateRange on open cube should not fail for default user', async () => { + const result = await defaultClient.load({ + measures: ['orders_open.count'], + timeDimensions: [{ + dimension: 'orders_open.created_at', + compareDateRange: [ + ['2020-01-01', '2020-06-30'], + ['2020-07-01', '2020-12-31'], + ], + }], + }, { queryType: 'multi' } as any); + expect(result).toBeDefined(); + }); }); }); diff --git a/rust/cubeorchestrator/src/query_result_transform.rs b/rust/cubeorchestrator/src/query_result_transform.rs index 56ff3aed74cab..9966ef400fea0 100644 --- a/rust/cubeorchestrator/src/query_result_transform.rs +++ b/rust/cubeorchestrator/src/query_result_transform.rs @@ -2329,6 +2329,62 @@ mod tests { Ok(()) } + #[test] + fn test_get_members_compare_date_range_empty_dataset_filter_only_td() -> Result<()> { + let mut test_data = TEST_SUITE_DATA + .get(&"compare_date_range_count_by_order_date".to_string()) + .unwrap() + .clone(); + + // Simulate a compareDateRange sub-query where the time dimension has + // no granularity (filter-only). After compareDateRangeTransformer, + // each sub-query has dateRange but no granularity. + test_data.request.query.time_dimensions = Some(vec![QueryTimeDimension { + dimension: "ECommerceRecordsUs2021.orderDate".to_string(), + date_range: Some(vec![ + "2020-01-01T00:00:00.000".to_string(), + "2020-01-31T23:59:59.999".to_string(), + ]), + compare_date_range: None, + granularity: None, + }]); + + // Remove TD-related entries from annotation since filter-only TDs + // don't produce annotation entries (prepareAnnotation skips TDs + // without granularity). + test_data + .request + .annotation + .remove("ECommerceRecordsUs2021.orderDate.day"); + test_data + .request + .annotation + .remove("ECommerceRecordsUs2021.orderDate"); + + let alias_to_member_name_map = &test_data.request.alias_to_member_name_map; + let annotation = &test_data.request.annotation; + let query = &test_data.request.query; + let query_type = &test_data.request.query_type.clone().unwrap_or_default(); + + let result = get_members( + query_type, + query, + &QueryResult { + columns: vec![], + rows: vec![], + columns_pos: IndexMap::new(), + }, + alias_to_member_name_map, + annotation, + ); + assert!( + result.is_ok(), + "compareDateRange with filter-only TD should not trigger hidden member error, got: {:?}", + result.err() + ); + Ok(()) + } + #[test] fn test_get_members_filled_dataset() -> Result<()> { let test_data = TEST_SUITE_DATA From 09da149c52bcaf9d398b770ada321c5f1a68f816 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Apr 2026 02:39:07 +0000 Subject: [PATCH 2/2] fix: correct test assertions for count with filter-only TDs COUNT aggregate returns a row with 0 even when no rows match the date range filter, not an empty array. Co-authored-by: Pavel Tiunov --- packages/cubejs-testing/test/smoke-rbac.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-testing/test/smoke-rbac.test.ts b/packages/cubejs-testing/test/smoke-rbac.test.ts index 3133e585bf0af..4ea247d88b84a 100644 --- a/packages/cubejs-testing/test/smoke-rbac.test.ts +++ b/packages/cubejs-testing/test/smoke-rbac.test.ts @@ -850,7 +850,9 @@ describe('Cube RBAC Engine', () => { dateRange: ['2099-01-01', '2099-12-31'], }], }); - expect(result.rawData()).toEqual([]); + // count returns a row with 0 even when no rows match the filter + expect(result.rawData().length).toBe(1); + expect(result.rawData()[0]['orders_open.count']).toBe('0'); }); test('compareDateRange with filter-only time dimension should work', async () => { @@ -914,7 +916,9 @@ describe('Cube RBAC Engine', () => { dateRange: ['2099-01-01', '2099-12-31'], }], }); - expect(result.rawData()).toEqual([]); + // count returns a row with 0 even when no rows match the filter + expect(result.rawData().length).toBe(1); + expect(result.rawData()[0]['orders_open.count']).toBe('0'); }); test('compareDateRange on open cube should not fail for default user', async () => {