1313import it .aboutbits .postgresql .core .SecretRef ;
1414import lombok .RequiredArgsConstructor ;
1515import org .jooq .DSLContext ;
16+ import org .jooq .Field ;
1617import org .junit .jupiter .api .DisplayName ;
1718import org .junit .jupiter .api .Test ;
19+ import org .junit .jupiter .params .ParameterizedTest ;
20+ import org .junit .jupiter .params .provider .Arguments ;
21+ import org .junit .jupiter .params .provider .MethodSource ;
1822
23+ import java .sql .SQLException ;
1924import java .time .OffsetDateTime ;
2025import java .time .ZoneOffset ;
2126import java .time .temporal .ChronoUnit ;
2227import java .util .concurrent .TimeUnit ;
28+ import java .util .function .BiConsumer ;
29+ import java .util .function .Predicate ;
30+ import java .util .stream .Stream ;
2331
2432import static it .aboutbits .postgresql .core .KubernetesUtil .SECRET_DATA_BASIC_AUTH_PASSWORD_KEY ;
33+ import static it .aboutbits .postgresql .core .infrastructure .persistence .Tables .PG_AUTHID ;
2534import static org .assertj .core .api .Assertions .assertThat ;
2635import static org .assertj .core .api .Assertions .within ;
2736import static org .awaitility .Awaitility .await ;
@@ -161,7 +170,7 @@ void createRole_withMissingClusterConnection_setsPending() {
161170 @ DisplayName (
162171 "When a Role (LOGIN) references a secret and that secret changes, it should trigger a re-reconciliation"
163172 )
164- void secretChange_triggersReconciliation () throws Exception {
173+ void secretChange_triggersReconciliation () throws SQLException {
165174 // given
166175 var clusterConnection = given .one ()
167176 .clusterConnection ()
@@ -200,17 +209,11 @@ void secretChange_triggersReconciliation() throws Exception {
200209 // Wait for password to match because reconciliation might take a bit
201210 await ().atMost (10 , TimeUnit .SECONDS )
202211 .pollInterval (500 , TimeUnit .MILLISECONDS )
203- .until (() -> {
204- try {
205- return PostgreSQLAuthenticationUtil .passwordMatches (
206- dsl ,
207- reconciled .getSpec (),
208- initialPassword
209- );
210- } catch (Exception e ) {
211- return false ;
212- }
213- });
212+ .until (() -> PostgreSQLAuthenticationUtil .passwordMatches (
213+ dsl ,
214+ reconciled .getSpec (),
215+ initialPassword
216+ ));
214217
215218 // when: update secret
216219 secret .getMetadata ().setManagedFields (null );
@@ -226,24 +229,18 @@ void secretChange_triggersReconciliation() throws Exception {
226229 // then: password should eventually match the new one
227230 await ().atMost (10 , TimeUnit .SECONDS )
228231 .pollInterval (500 , TimeUnit .MILLISECONDS )
229- .until (() -> {
230- try {
231- return PostgreSQLAuthenticationUtil .passwordMatches (
232- dsl ,
233- reconciled .getSpec (),
234- newPassword
235- );
236- } catch (Exception e ) {
237- return false ;
238- }
239- });
232+ .until (() -> PostgreSQLAuthenticationUtil .passwordMatches (
233+ dsl ,
234+ reconciled .getSpec (),
235+ newPassword
236+ ));
240237 }
241238
242239 @ Test
243240 @ DisplayName (
244241 "When a Role (LOGIN) changes its secret reference, it should trigger a re-reconciliation"
245242 )
246- void secretRefChange_triggersReconciliation () throws Exception {
243+ void secretRefChange_triggersReconciliation () throws SQLException {
247244 // given
248245 var clusterConnection = given .one ()
249246 .clusterConnection ()
@@ -282,17 +279,11 @@ void secretRefChange_triggersReconciliation() throws Exception {
282279 var finalRole = reconciled ;
283280 await ().atMost (10 , TimeUnit .SECONDS )
284281 .pollInterval (500 , TimeUnit .MILLISECONDS )
285- .until (() -> {
286- try {
287- return PostgreSQLAuthenticationUtil .passwordMatches (
288- dsl ,
289- finalRole .getSpec (),
290- initialPassword
291- );
292- } catch (Exception e ) {
293- return false ;
294- }
295- });
282+ .until (() -> PostgreSQLAuthenticationUtil .passwordMatches (
283+ dsl ,
284+ finalRole .getSpec (),
285+ initialPassword
286+ ));
296287
297288 // when: update secret reference in the Role
298289 role .getSpec ().setPasswordSecretRef (newSecretRef );
@@ -303,20 +294,110 @@ void secretRefChange_triggersReconciliation() throws Exception {
303294 var updatedRole = reconciled ;
304295 await ().atMost (10 , TimeUnit .SECONDS )
305296 .pollInterval (500 , TimeUnit .MILLISECONDS )
306- .until (() -> {
307- try {
308- return PostgreSQLAuthenticationUtil .passwordMatches (
309- dsl ,
310- updatedRole .getSpec (),
311- newPassword
312- );
313- } catch (Exception e ) {
314- return false ;
315- }
316- });
297+ .until (() -> PostgreSQLAuthenticationUtil .passwordMatches (
298+ dsl ,
299+ updatedRole .getSpec (),
300+ newPassword
301+ ));
302+ }
303+
304+ @ ParameterizedTest
305+ @ MethodSource ("provideBooleanFlags" )
306+ @ DisplayName ("When a boolean Role flag is toggled, it should be updated in the database" )
307+ void roleFlag_togglesCorrectly (
308+ Field <Boolean > field ,
309+ BiConsumer <RoleSpec .Flags , Boolean > setter
310+ ) throws SQLException {
311+ // given
312+ var clusterConnection = given .one ()
313+ .clusterConnection ()
314+ .withName ("test-role-flags" )
315+ .returnFirst ();
316+
317+ var dsl = postgreSQLContextFactory .getDSLContext (clusterConnection );
318+
319+ var roleName = "test-role-" + field .getName ();
320+
321+ var role = buildRole (
322+ roleName ,
323+ clusterConnection .getMetadata ().getName (),
324+ /*login*/ false
325+ );
326+
327+ // 1. Enable flag (true)
328+ setter .accept (
329+ role .getSpec ().getFlags (),
330+ true
331+ );
332+
333+ // when
334+ var reconciled = applyRole (role );
335+ var initialGeneration = reconciled .getStatus ().getObservedGeneration ();
336+
337+ // then
338+ assertThat (
339+ getRoleFlagValue (
340+ dsl ,
341+ roleName ,
342+ field
343+ )
344+ ).isTrue ();
345+
346+ // 2. Disable flag (false)
347+ setter .accept (
348+ role .getSpec ().getFlags (),
349+ false
350+ );
351+
352+ // when
353+ applyRole (
354+ role ,
355+ r -> r .getStatus ().getObservedGeneration () == initialGeneration + 1
356+ );
357+
358+ // then
359+ assertThat (
360+ getRoleFlagValue (
361+ dsl ,
362+ roleName ,
363+ field
364+ )
365+ ).isFalse ();
366+ }
367+
368+ private Boolean getRoleFlagValue (
369+ DSLContext dsl ,
370+ String roleName ,
371+ Field <Boolean > field
372+ ) {
373+ return dsl .select (field )
374+ .from (PG_AUTHID )
375+ .where (PG_AUTHID .ROLNAME .eq (roleName ))
376+ .fetchSingleInto (field .getType ());
377+ }
378+
379+ private static Stream <Arguments > provideBooleanFlags () {
380+ return Stream .of (
381+ Arguments .of (PG_AUTHID .ROLSUPER , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setSuperuser ),
382+ Arguments .of (PG_AUTHID .ROLCREATEDB , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setCreatedb ),
383+ Arguments .of (PG_AUTHID .ROLCREATEROLE , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setCreaterole ),
384+ Arguments .of (PG_AUTHID .ROLINHERIT , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setInherit ),
385+ Arguments .of (PG_AUTHID .ROLREPLICATION , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setReplication ),
386+ Arguments .of (PG_AUTHID .ROLBYPASSRLS , (BiConsumer <RoleSpec .Flags , Boolean >) RoleSpec .Flags ::setBypassrls )
387+ );
317388 }
318389
319390 private Role applyRole (Role role ) {
391+ return applyRole (
392+ role ,
393+ r -> r .getStatus () != null
394+ );
395+ }
396+
397+ private Role applyRole (
398+ Role role ,
399+ Predicate <Role > condition
400+ ) {
320401 var namespace = kubernetesClient .getNamespace ();
321402
322403 kubernetesClient .resources (Role .class )
@@ -328,7 +409,7 @@ private Role applyRole(Role role) {
328409 .inNamespace (namespace )
329410 .withName (role .getName ())
330411 .waitUntilCondition (
331- r -> r . getStatus () != null ,
412+ condition ,
332413 10 ,
333414 TimeUnit .SECONDS
334415 );
0 commit comments