Files
dc/sas/sasjs/services/editors/getdata.test.2.sas
T
dc 34691dceec
Build / Build-and-ng-test (pull_request) Failing after 3m13s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 19m53s
feat(editor): serve a truncated selection with a warning instead of aborting
A selection over DC_MAXOBS_WEBEDIT or DC_MAXCELLS_WEBEDIT was refused with
"Table is too big", leaving the user with no grid and no way to see what the
filter had matched.  getdata now serves the first rows that fit and flags it:

- the row cap is min(DC_MAXOBS_WEBEDIT, DC_MAXCELLS_WEBEDIT / columns), so a
  wide table is still bounded by the cell budget
- sasparams carries TRUNCATED (0/1) and TRUNC_MSG, naming the limit reached
- the EDIT screen shows a warning triangle next to the table name, with the
  message in a tooltip and the advice to filter

The mock mirrors the new behaviour, the Cypress spec asserts the truncated
grid and the tooltip (real hover, opacity checked so a hidden tooltip cannot
pass), and the SAS limit test now asserts the served row counts and message
rather than a cancelled child job.
2026-10-22 16:38:37 +00:00

272 lines
8.3 KiB
SAS

/**
@file
@brief testing that selectbox (cell dropdown) values respect Row Level Security
@details A HARDSELECT / SOFTSELECT rule whose RULE_VALUE is a
library.member.column reference has its distinct values read straight from
that source table by getdata.sas. If the source table carries Row Level
Security rules, the restricted rows must be filtered out of the dropdown too -
otherwise values the user is not permitted to see appear in the cell dropdown
while the grid itself is correctly filtered.
This test makes the runner a 'regular' (non admin) user and restricts
SOME_BESTNUM in MPE_X_TEST to one value selected from the data (the demo
data holds random SOME_BESTNUM values, refreshed by testsetup, so the
value cannot be hardcoded). The demo data must hold more than one
distinct SOME_BESTNUM - true of the SAS-seeded estate, but NOT of the
SASjs server mock, which seeds the seven 44 rows only (see the
'distinct_bestnum' precondition assertion below).
Runners whose SAS identity group is the admin group cannot be demoted by
editing MPE_GROUPS alone - Row Level Security is skipped for them - so the
test reports a skip row instead of running the assertions.
The call passes a filter (PRIMARY_KEY_FIELD < 100) so the whole selection is
served - the unfiltered MPE_X_TEST is above the row limit (642 rows against
DC_MAXOBS_WEBEDIT:250) and would come back truncated. The limits test
(getdata.test.1.sas) covers that truncation separately.
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mf_getuser.sas
@li mpe_getgroups.sas
@li mp_assert.sas
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@li mp_filterstore.sas
@li mx_execute.sas
**/
%let _program=&appLoc/services/editors/getdata;
/**
* The body is wrapped in a macro so the conditional skip below uses
* %if/%then/%do inside a macro definition. Open-code %if needs SAS 9.4M5+,
* and the suite also runs on the sas9 target, so the wrapper keeps the test
* portable across the supported estates (as testsetup.sas's update_settings
* does for its own conditionals).
*/
%macro getdata_rls_test();
/**
* Row Level Security is skipped for admins, so only a non admin runner can
* verify it. The group membership is checked against &dc_admin_group (the
* mpeinit-resolved &mpeadmins is not available in a test session, see
* testinit.sas) and taken from the SAS identity groups, NOT the MPE_GROUPS
* table this test goes on to edit. Admin runners (eg a shared test account
* that is a member of the admin group) simply skip the RLS assertions.
*/
%mpe_getgroups(user=%mf_getuser(),outds=work.testgroups)
proc sql noprint;
select count(*) into: admin_check
from work.testgroups
where groupname="&dc_admin_group";
quit;
%if &admin_check>0 %then %do;
%put WARNING: %mf_getuser() is in the &dc_admin_group group;
%put WARNING: Row Level Security is skipped for admins - test skipped;
data work.test_results;
length test_result $4 test_description $256 test_comments $256;
test_result='PASS';
test_description="GETDATA_TEST_2 skipped - runner is an admin";
test_comments="RLS is skipped for admins so the assertions cannot run";
run;
%end;
%else %do;
/**
* Make the runner a 'regular' user and grant the temp group EDIT access so
* the getdata access check still passes.
*/
data work.groups_backup;
set &dc_libref..MPE_GROUPS;
where upcase(user_name)="%upcase(%mf_getuser())";
run;
%let tempgroup=%mf_getuniquename();
proc sql;
delete from &dc_libref..MPE_GROUPS
where upcase(user_name)="%upcase(%mf_getuser())";
insert into &dc_libref..MPE_GROUPS
set user_name="%mf_getuser()"
,group_name="&tempgroup"
,group_desc="temp group"
,tx_from='01Jan1960:00:00:00'dt
,tx_to='31Dec9999:23:59:59'dt;
insert into &dc_libref..MPE_SECURITY
set tx_from=0
,libref="*ALL*"
,dsn="*ALL*"
,access_level="EDIT"
,sas_group="&tempgroup"
,tx_to='31DEC9999:23:59:59'dt;
quit;
/**
* Row Level Security on MPE_X_TEST - only one SOME_BESTNUM value (selected
* from the data, see header) is visible. The value is taken from the rows
* that match the user filter below, so the filtered grid cannot be empty.
*/
proc sql noprint;
delete from &dc_libref..MPE_ROW_LEVEL_SECURITY
where RLS_TABLE="MPE_X_TEST" and rls_group="&tempgroup";
select distinct some_bestnum into :rls_value trimmed
from &dc_libref..MPE_X_TEST
where some_bestnum is not null and primary_key_field < 100;
select coalesce(max(rls_rk),0) into: max_rk
from &dc_libref..MPE_ROW_LEVEL_SECURITY;
quit;
proc sql;
insert into &dc_libref..mpe_row_level_security set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,RLS_RK=&max_rk+1
,RLS_SCOPE='ALL'
,RLS_GROUP="&tempgroup"
,RLS_LIBREF="&dc_libref"
,RLS_TABLE="MPE_X_TEST"
,RLS_GROUP_LOGIC='AND'
,RLS_SUBGROUP_LOGIC='OR'
,RLS_SUBGROUP_ID=0
,RLS_VARIABLE_NM='SOME_BESTNUM'
,RLS_OPERATOR_NM='IN'
,RLS_RAW_VALUE="(&rls_value)"
,RLS_ACTIVE=1;
quit;
/**
* Preconditions - the source column really does hold more than one distinct
* value (so the assertions are meaningful) and the user filter matches at
* least one of them (so the grid cannot be empty).
*/
proc sql noprint;
select count(distinct some_bestnum) into: distinct_bestnum
from &dc_libref..MPE_X_TEST;
quit;
%mp_assert(
iftrue=(&distinct_bestnum>1),
desc=Precondition - MPE_X_TEST.SOME_BESTNUM has more than one distinct value,
outds=work.test_results
)
%mp_assert(
iftrue=(%length(&rls_value)>0),
desc=Precondition - the user filter matches at least one SOME_BESTNUM value,
outds=work.test_results
)
/**
* Store a filter (PRIMARY_KEY_FIELD < 100) so the selection stays under the
* DC_MAXOBS_WEBEDIT row limit.
*/
data work.inquery;
length GROUP_LOGIC $3 SUBGROUP_LOGIC $3 SUBGROUP_ID 8
VARIABLE_NM $32 OPERATOR_NM $10 RAW_VALUE $4000;
GROUP_LOGIC='AND';
SUBGROUP_LOGIC='AND';
SUBGROUP_ID=1;
VARIABLE_NM='PRIMARY_KEY_FIELD';
OPERATOR_NM='<';
RAW_VALUE='100';
run;
%mp_filterstore(
libds=&dclib..MPE_X_TEST,
filter_summary=&dc_libref..mpe_filteranytable,
filter_detail=&dc_libref..mpe_filtersource,
lock_table=&dc_libref..mpe_lockanytable,
maxkeytable=&dc_libref..mpe_maxkeyvalues,
queryds=work.inquery,
outresult=work.filter_result,
outquery=work.filter_query
)
data _null_;
set work.filter_result;
call symputx('f1_rk',filter_rk,'l');
run;
/**
* Call the getdata service - filtered to keep the row count under
* DC_MAXOBS_WEBEDIT (see header).
*/
%let f1=%mf_getuniquefileref();
data _null_;
file &f1 termstr=crlf;
put 'LIBDS:$43. FILTER_RK:8.';
put "&dclib..MPE_X_TEST,&f1_rk";
run;
%mx_execute(&_program,
viyacontext=&defaultcontext,
inputfiles=&f1:sascontroltable,
outlib=web1,
mdebug=&sasjs_mdebug
)
data work.dqdata;
set web1.dqdata;
run;
data work.sasdata;
set web1.sasdata;
run;
/* the selectbox source rows only (exclude MPE_SELECTBOX-derived values) */
data work.dqtest;
set work.dqdata;
where upcase(base_col)='SOME_BESTNUM';
run;
data work.checkchar;
val="&rls_value"; output;
run;
data work.checknum;
val=&rls_value; output;
run;
/**
* Test 1 - control: the grid itself is filtered by RLS. If this fails the
* runner is an admin and the dropdown assertion below cannot be trusted.
*/
%mp_assertcolvals(work.sasdata.SOME_BESTNUM,
checkvals=work.checknum.val,
desc=Test 1 - grid rows respect Row Level Security (control),
test=ALLVALS,
outds=work.test_results
)
/**
* Test 2 - the selectbox values sourced from MPE_X_TEST.SOME_BESTNUM must
* also be restricted to the RLS-visible value (44).
*/
%mp_assertdsobs(work.dqtest,
desc=Test 2 precondition - selectbox values were returned for SOME_BESTNUM,
test=HASOBS,
outds=work.test_results
)
%mp_assertcolvals(work.dqtest.RULE_DATA,
checkvals=work.checkchar.val,
desc=Test 2 - selectbox values respect Row Level Security,
test=ALLVALS,
outds=work.test_results
)
/**
* Cleanup - restore the group / security / RLS rows
*/
proc sql;
delete from &dc_libref..MPE_ROW_LEVEL_SECURITY where rls_group="&tempgroup";
delete from &dc_libref..MPE_SECURITY where sas_group="&tempgroup";
delete from &dc_libref..MPE_GROUPS where group_name="&tempgroup";
quit;
proc append base=&dc_libref..MPE_GROUPS data=work.groups_backup;
run;
%end;
%mend getdata_rls_test;
%getdata_rls_test()