3737-include (" ns_common.hrl" ).
3838-include (" cut.hrl" ).
3939
40+ -ifdef (TEST ).
41+ -include_lib (" eunit/include/eunit.hrl" ).
42+ -endif .
43+
4044-define (VERSION_1 , " v1" ).
45+ -define (WORKER_SYNC_TIMEOUT , 5000 ).
4146
4247handle_rpc_connect (? VERSION_1 , Label , Req ) ->
4348 case ns_config_auth :is_system_provisioned () of
@@ -76,6 +81,9 @@ sync() ->
7681 sync (node ()).
7782
7883sync (Node ) ->
84+ sync (Node , ? WORKER_SYNC_TIMEOUT ).
85+
86+ sync (Node , WorkerSyncTimeout ) ->
7987 InternalConnections =
8088 gen_server :call ({? MODULE , Node }, get_internal_connections , infinity ),
8189 % % If the above call succeeds, but a worker exits before we make the below
@@ -86,7 +94,7 @@ sync(Node) ->
8694 % % caller
8795 misc :parallel_map (
8896 fun (Pid ) ->
89- try menelaus_cbauth_worker :sync (Pid )
97+ try menelaus_cbauth_worker :sync (Pid , WorkerSyncTimeout )
9098 catch exit :{noproc , _ } ->
9199 ? log_error (" Process ~p no longer exists" , [Pid ])
92100 end
@@ -639,3 +647,182 @@ service_to_label(xdcr) ->
639647 " goxdcr-cbauth" ;
640648service_to_label (Service ) ->
641649 atom_to_list (Service ) ++ " -cbauth" .
650+
651+ -ifdef (TEST ).
652+ -define (SERVICE , " <service>" ).
653+ -define (LABEL , " <service>-cbauth" ).
654+ -define (HEARTBEAT_TIME_S , 1 ).
655+ % % Short timeout for waiting to confirm that something doesn't immediately
656+ % % happen, without waiting so long that it significantly increases test duration
657+ -define (SHORT_TIMEOUT , 500 ).
658+ % % Timeout for things that should eventually happen, but not necessarily
659+ % % immediately
660+ -define (LONG_TIMEOUT , 60_000 ).
661+
662+ setup_t () ->
663+ meck :expect (config_profile , get ,
664+ fun () ->
665+ ? DEFAULT_EMPTY_PROFILE_FOR_TESTS
666+ end ),
667+
668+ fake_ns_config :setup (),
669+ fake_chronicle_kv :setup (),
670+ % % Test setups return a map of pids for later shutdown in the teardown
671+ PidMap = mock_helpers :setup_mocks ([json_rpc_events ,
672+ ns_node_disco_events ,
673+ user_storage_events ,
674+ ssl_service_events ,
675+ json_rpc_connection_sup ,
676+ ns_ssl_services_setup ,
677+ menelaus_users ,
678+ ns_secrets ,
679+ testconditions ]),
680+
681+ % % Set config values for a few keys, since these are needed for greater
682+ % % coverage, and to avoid errors
683+ fake_chronicle_kv :update_snapshot (#{nodes_wanted => [node ()],
684+ bucket_names => []}),
685+ fake_ns_config :update_snapshot ([{rest , [{port , 8091 }]},
686+ {rest_creds , placeholder },
687+ {memcached , [{admin_user , " user" },
688+ {admin_pass , " pass" }]}]),
689+
690+ meck :new (menelaus_cbauth_worker , [passthrough ]),
691+
692+ {ok , Pid } = menelaus_cbauth :start_link (),
693+ start_fake_json_rpc_connection (? LABEL ),
694+ PidMap #{? MODULE => Pid }.
695+
696+ teardown_t (PidMap ) ->
697+ Name = list_to_atom (" json_rpc_connection-" ++ ? LABEL ),
698+ erlang :unregister (Name ),
699+ mock_helpers :shutdown_processes (PidMap ),
700+ fake_chronicle_kv :teardown (),
701+ fake_ns_config :teardown (),
702+ meck :unload ().
703+
704+ start_fake_json_rpc_connection (Label ) ->
705+ Name = list_to_atom (" json_rpc_connection-" ++ Label ),
706+ Pid = self (),
707+ true = erlang :register (Name , Pid ),
708+ meck :expect (json_rpc_connection , perform_call ,
709+ fun (_Label , _Call , _EJsonArg , _Opts ) ->
710+ {ok , true }
711+ end ),
712+ gen_event :notify (json_rpc_events ,
713+ {started , Label , [internal ,
714+ {heartbeat , ? HEARTBEAT_TIME_S }],
715+ self ()}).
716+
717+ cbauth_init_t () ->
718+ % % UpdateDB gets called once almost immediately
719+ meck :wait (json_rpc_connection , perform_call ,
720+ ['_' , " AuthCacheSvc.UpdateDB" , '_' , '_' ], ? LONG_TIMEOUT ),
721+ % % Heartbeat gets called once
722+ meck :wait (json_rpc_connection , perform_call ,
723+ ['_' , " AuthCacheSvc.Heartbeat" , '_' , '_' ],
724+ 2_000 * ? HEARTBEAT_TIME_S ),
725+ % % Heartbeat gets called again
726+ meck :reset (json_rpc_connection ),
727+ meck :wait (json_rpc_connection , perform_call ,
728+ ['_' , " AuthCacheSvc.Heartbeat" , '_' , '_' ],
729+ 2_000 * ? HEARTBEAT_TIME_S ),
730+ % % UpdateDB isn't called immediately again
731+ ? assertError (timeout ,
732+ meck :wait (json_rpc_connection , perform_call ,
733+ ['_' , " AuthCacheSvc.UpdateDB" , '_' , '_' ],
734+ ? SHORT_TIMEOUT )).
735+
736+ cbauth_sync_t () ->
737+ % % UpdateDB gets called once immediately
738+ meck :wait (json_rpc_connection , perform_call ,
739+ ['_' , " AuthCacheSvc.UpdateDB" , '_' , '_' ],
740+ ? LONG_TIMEOUT ),
741+ [ok ] = sync (),
742+ % % Ensure that the next perform_call doesn't immediately return until after
743+ % % the sync call would time out
744+ meck :expect (json_rpc_connection , perform_call ,
745+ fun (_ , " AuthCacheSvc.UpdateDB" , _ , _ ) ->
746+ timer :sleep (2 * ? SHORT_TIMEOUT );
747+ (_ , _ , _ , _ ) ->
748+ ok
749+ end ),
750+ meck :reset (menelaus_cbauth_worker ),
751+ % % Force an update by updating the snapshot
752+ fake_ns_config :update_snapshot (rest , [{port , 8092 }]),
753+ % % Wait for the update to start being handled
754+ meck :wait (menelaus_cbauth_worker , notify , ['_' , '_' ], ? LONG_TIMEOUT ),
755+ % % Sync with timeout half the perform_call sleep time, to ensure it gets hit
756+ ? assertExit ({timeout , _ }, sync (node (), ? SHORT_TIMEOUT )).
757+
758+ cbauth_stats_t () ->
759+ meck :expect (json_rpc_connection , perform_call ,
760+ fun (_Label , " AuthCacheSvc.GetStats" , _EJsonArg , _Opts ) ->
761+ {ok , {[ok ]}};
762+ (_Label , _Call , _EJsonArg , _Opts ) ->
763+ {ok , true }
764+ end ),
765+ [{<<? SERVICE >>, ok }] = stats (),
766+ meck :expect (json_rpc_connection , perform_call ,
767+ fun (_Label , " AuthCacheSvc.GetStats" , _EJsonArg , _Opts ) ->
768+ {error , error };
769+ (_Label , _Call , _EJsonArg , _Opts ) ->
770+ {ok , true }
771+ end ),
772+ [] = stats ().
773+
774+ trigger_notification (ns_node_disco_events ) ->
775+ % % To avoid needing to trick ns_node_disco into recognising fake nodes, just
776+ % % make the node list different by removing the only node.
777+ % % Note, this tests ns_node_disco_events as well as chronicle_kv, as the
778+ % % event handler for chronicle_compat_events in this module does not cover
779+ % % the nodes_wanted key (although the ns_node_disco handler does cover this
780+ % % key, hence how this tests the ns_node_disco_events handler)
781+ fake_chronicle_kv :update_snapshot (nodes_wanted , []);
782+ trigger_notification (ns_config_events ) ->
783+ % % This is just an arbitrary key in ns_config that we use for the cbauth
784+ % % info, that is covered by the chronicle_compat_events handler
785+ fake_ns_config :update_snapshot (rest , [{port , 8092 }]);
786+ trigger_notification (chronicle_kv ) ->
787+ % % While the bucket_names key isn't subscribed to, the collections key is,
788+ % % and we need to update both for the info to be updated
789+ fake_chronicle_kv :update_snapshot (
790+ #{bucket_names => [" test" ],
791+ {bucket , " test" , collections } => [{uid , 0 }],
792+ {bucket , " test" , props } => [{type , membase }]});
793+ trigger_notification (user_storage_events ) ->
794+ meck :expect (menelaus_users , get_users_version , 0 , {1 , 0 }),
795+ % % It isn't worth the complexity to trigger a user version change through
796+ % % less artificial means, so just manually trigger the event
797+ gen_event :notify (user_storage_events , event );
798+ trigger_notification (ssl_service_events ) ->
799+ % % It isn't worth the complexity to trigger an ssl_service_event through
800+ % % less artificial means, so just manually trigger the event
801+ gen_event :notify (ssl_service_events , client_cert_changed ).
802+
803+ cbauth_notify_tests () ->
804+ % % Check that expected events cause notifications
805+ [{" cbauth notify " ++ atom_to_list (EventManager ) ++ " test" ,
806+ fun () ->
807+ % % UpdateDB gets called once almost immediately
808+ meck :wait (json_rpc_connection , perform_call ,
809+ ['_' , " AuthCacheSvc.UpdateDB" , '_' , '_' ],
810+ ? LONG_TIMEOUT ),
811+ meck :reset (json_rpc_connection ),
812+ trigger_notification (EventManager ),
813+ meck :wait (json_rpc_connection , perform_call ,
814+ ['_' , " AuthCacheSvc.UpdateDB" , '_' , '_' ],
815+ ? LONG_TIMEOUT )
816+ end } || EventManager <- [ns_node_disco_events ,
817+ ns_config_events ,
818+ chronicle_kv ,
819+ user_storage_events ,
820+ ssl_service_events ]].
821+
822+ cbauth_test_ () ->
823+ {foreach , fun setup_t /0 , fun teardown_t /1 ,
824+ [{" cbauth init test" , fun cbauth_init_t /0 },
825+ {" cbauth sync test" , fun cbauth_sync_t /0 },
826+ {" cbauth stats test" , fun cbauth_stats_t /0 }
827+ | cbauth_notify_tests ()]}.
828+ -endif .
0 commit comments