| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606 |
- %%--------------------------------------------------------------------
- %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
- %%
- %% Licensed under the Apache License, Version 2.0 (the "License");
- %% you may not use this file except in compliance with the License.
- %% You may obtain a copy of the License at
- %%
- %% http://www.apache.org/licenses/LICENSE-2.0
- %%
- %% Unless required by applicable law or agreed to in writing, software
- %% distributed under the License is distributed on an "AS IS" BASIS,
- %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- %% See the License for the specific language governing permissions and
- %% limitations under the License.
- %%--------------------------------------------------------------------
- -module(emqx_rule_engine_SUITE).
- -compile(export_all).
- -compile(nowarn_export_all).
- -include_lib("eunit/include/eunit.hrl").
- -include_lib("common_test/include/ct.hrl").
- -include_lib("snabbkaffe/include/snabbkaffe.hrl").
- -include_lib("emqx/include/emqx.hrl").
- -import(emqx_common_test_helpers, [on_exit/1]).
- %%-define(PROPTEST(M,F), true = proper:quickcheck(M:F())).
- -define(TMP_RULEID, atom_to_binary(?FUNCTION_NAME)).
- all() ->
- [
- {group, engine},
- {group, funcs},
- {group, registry},
- {group, runtime},
- {group, events},
- {group, telemetry},
- {group, bugs},
- {group, metrics},
- {group, metrics_simple},
- {group, metrics_fail},
- {group, metrics_fail_simple}
- ].
- suite() ->
- [{ct_hooks, [cth_surefire]}, {timetrap, {seconds, 30}}].
- groups() ->
- [
- {engine, [sequence], [t_create_rule]},
- {funcs, [], [t_kv_store]},
- {registry, [sequence], [
- t_add_get_remove_rule,
- t_add_get_remove_rules,
- t_create_existing_rule,
- t_get_rules_for_topic,
- t_get_rules_for_topic_2,
- t_get_rules_with_same_event,
- t_get_rule_ids_by_action,
- t_ensure_action_removed
- ]},
- {runtime, [], [
- t_match_atom_and_binary,
- t_sqlselect_0,
- t_sqlselect_00,
- t_sqlselect_with_3rd_party_impl,
- t_sqlselect_with_3rd_party_impl2,
- t_sqlselect_with_3rd_party_funcs_unknown,
- t_sqlselect_001,
- t_sqlselect_002,
- t_sqlselect_inject_props,
- t_sqlselect_01,
- t_sqlselect_02,
- t_sqlselect_1,
- t_sqlselect_2,
- t_sqlselect_3,
- t_sqlselect_message_publish_event_keep_original_props_1,
- t_sqlselect_message_publish_event_keep_original_props_2,
- t_sqlparse_event_1,
- t_sqlparse_event_2,
- t_sqlparse_event_3,
- t_sqlparse_foreach_1,
- t_sqlparse_foreach_2,
- t_sqlparse_foreach_3,
- t_sqlparse_foreach_4,
- t_sqlparse_foreach_5,
- t_sqlparse_foreach_6,
- t_sqlparse_foreach_7,
- t_sqlparse_foreach_8,
- t_sqlparse_case_when_1,
- t_sqlparse_case_when_2,
- t_sqlparse_case_when_3,
- t_sqlparse_array_index_1,
- t_sqlparse_array_index_2,
- t_sqlparse_array_index_3,
- t_sqlparse_array_index_4,
- t_sqlparse_array_index_5,
- t_sqlparse_select_matadata_1,
- t_sqlparse_array_range_1,
- t_sqlparse_array_range_2,
- t_sqlparse_true_false,
- t_sqlparse_undefined_variable,
- t_sqlparse_new_map,
- t_sqlparse_invalid_json
- ]},
- {events, [], [
- t_events,
- t_event_client_disconnected_normal,
- t_event_client_disconnected_kicked,
- t_event_client_disconnected_discarded,
- t_event_client_disconnected_takenover
- ]},
- {telemetry, [], [
- t_get_basic_usage_info_0,
- t_get_basic_usage_info_1
- ]},
- {bugs, [], [
- t_sqlparse_payload_as,
- t_sqlparse_nested_get
- ]},
- {metrics, [], [
- t_rule_metrics_sync,
- t_rule_metrics_async
- ]},
- {metrics_simple, [], [
- t_rule_metrics_sync,
- t_rule_metrics_async
- ]},
- {metrics_fail, [], [
- t_rule_metrics_sync_fail,
- t_rule_metrics_async_fail
- ]},
- {metrics_fail_simple, [], [
- t_rule_metrics_sync_fail,
- t_rule_metrics_async_fail
- ]}
- ].
- %%------------------------------------------------------------------------------
- %% Overall setup/teardown
- %%------------------------------------------------------------------------------
- init_per_suite(Config) ->
- %% ensure module loaded
- emqx_rule_funcs_demo:module_info(),
- application:load(emqx_conf),
- ok = emqx_common_test_helpers:start_apps(
- [emqx_conf, emqx_rule_engine, emqx_authz, emqx_bridge],
- fun set_special_configs/1
- ),
- Config.
- end_per_suite(_Config) ->
- emqx_common_test_helpers:stop_apps([emqx_conf, emqx_rule_engine]),
- ok.
- set_special_configs(emqx_authz) ->
- {ok, _} = emqx:update_config(
- [authorization],
- #{
- <<"no_match">> => atom_to_binary(allow),
- <<"cache">> => #{<<"enable">> => atom_to_binary(true)},
- <<"sources">> => []
- }
- ),
- ok;
- set_special_configs(_) ->
- ok.
- on_resource_create(_id, _) -> #{}.
- on_resource_destroy(_id, _) -> ok.
- on_get_resource_status(_id, _) -> #{}.
- %%------------------------------------------------------------------------------
- %% Group specific setup/teardown
- %%------------------------------------------------------------------------------
- group(_Groupname) ->
- [].
- -define(BRIDGE_IMPL, emqx_bridge_mqtt_connector).
- init_per_group(registry, Config) ->
- Config;
- init_per_group(metrics_fail, Config) ->
- meck:expect(?BRIDGE_IMPL, on_query, 3, {error, {unrecoverable_error, mecked_failure}}),
- meck:expect(?BRIDGE_IMPL, on_query_async, 4, {error, {unrecoverable_error, mecked_failure}}),
- [{mecked, [?BRIDGE_IMPL]} | Config];
- init_per_group(metrics_simple, Config) ->
- meck:new(?BRIDGE_IMPL, [non_strict, no_link, passthrough]),
- meck:expect(?BRIDGE_IMPL, query_mode, fun
- (#{resource_opts := #{query_mode := sync}}) -> simple_sync;
- (_) -> simple_async
- end),
- [{mecked, [?BRIDGE_IMPL]} | Config];
- init_per_group(metrics_fail_simple, Config) ->
- meck:new(?BRIDGE_IMPL, [non_strict, no_link, passthrough]),
- meck:expect(?BRIDGE_IMPL, query_mode, fun
- (#{resource_opts := #{query_mode := sync}}) -> simple_sync;
- (_) -> simple_async
- end),
- meck:expect(?BRIDGE_IMPL, on_query, 3, {error, {unrecoverable_error, mecked_failure}}),
- meck:expect(?BRIDGE_IMPL, on_query_async, fun(_, _, {ReplyFun, Args}, _) ->
- Result = {error, {unrecoverable_error, mecked_failure}},
- erlang:apply(ReplyFun, Args ++ [Result]),
- Result
- end),
- [{mecked, [?BRIDGE_IMPL]} | Config];
- init_per_group(_Groupname, Config) ->
- Config.
- end_per_group(_Groupname, Config) ->
- case ?config(mecked, Config) of
- undefined -> ok;
- Mecked -> meck:unload(Mecked)
- end.
- %%------------------------------------------------------------------------------
- %% Testcase specific setup/teardown
- %%------------------------------------------------------------------------------
- init_per_testcase(t_events, Config) ->
- init_events_counters(),
- SQL =
- "SELECT * FROM \"$events/client_connected\", "
- "\"$events/client_disconnected\", "
- "\"$events/client_connack\", "
- "\"$events/client_check_authz_complete\", "
- "\"$events/session_subscribed\", "
- "\"$events/session_unsubscribed\", "
- "\"$events/message_acked\", "
- "\"$events/message_delivered\", "
- "\"$events/message_dropped\", "
- "\"$events/delivery_dropped\", "
- "\"t1\"",
- {ok, Rule} = emqx_rule_engine:create_rule(
- #{
- id => <<"rule:t_events">>,
- sql => SQL,
- actions => [
- #{
- function => <<"emqx_rule_engine_SUITE:action_record_triggered_events">>,
- args => #{}
- }
- ],
- description => <<"to console and record triggered events">>
- }
- ),
- ?assertMatch(#{id := <<"rule:t_events">>}, Rule),
- [{hook_points_rules, Rule} | Config];
- init_per_testcase(_TestCase, Config) ->
- Config.
- end_per_testcase(t_events, Config) ->
- ets:delete(events_record_tab),
- ok = delete_rule(?config(hook_points_rules, Config)),
- emqx_common_test_helpers:call_janitor(),
- ok;
- end_per_testcase(_TestCase, _Config) ->
- emqx_common_test_helpers:call_janitor(),
- ok.
- %%------------------------------------------------------------------------------
- %% Test cases for rule engine
- %%------------------------------------------------------------------------------
- t_create_rule(_Config) ->
- {ok, #{id := Id}} = emqx_rule_engine:create_rule(
- #{
- sql => <<"select * from \"t/a\"">>,
- id => <<"t_create_rule">>,
- actions => [#{function => console}],
- description => <<"debug rule">>
- }
- ),
- ct:pal("======== emqx_rule_engine:get_rules :~p", [emqx_rule_engine:get_rules()]),
- ?assertMatch(
- {ok, #{id := Id, from := [<<"t/a">>]}},
- emqx_rule_engine:get_rule(Id)
- ),
- delete_rule(Id),
- ok.
- %%------------------------------------------------------------------------------
- %% Test cases for rule funcs
- %%------------------------------------------------------------------------------
- t_kv_store(_) ->
- undefined = emqx_rule_funcs:kv_store_get(<<"abc">>),
- <<"not_found">> = emqx_rule_funcs:kv_store_get(<<"abc">>, <<"not_found">>),
- emqx_rule_funcs:kv_store_put(<<"abc">>, 1),
- 1 = emqx_rule_funcs:kv_store_get(<<"abc">>),
- emqx_rule_funcs:kv_store_del(<<"abc">>),
- undefined = emqx_rule_funcs:kv_store_get(<<"abc">>).
- %%------------------------------------------------------------------------------
- %% Test cases for rule registry
- %%------------------------------------------------------------------------------
- t_add_get_remove_rule(_Config) ->
- RuleId0 = <<"rule-debug-0">>,
- ok = create_rule(make_simple_rule(RuleId0)),
- ?assertMatch({ok, #{id := RuleId0}}, emqx_rule_engine:get_rule(RuleId0)),
- ok = delete_rule(RuleId0),
- ?assertEqual(not_found, emqx_rule_engine:get_rule(RuleId0)),
- RuleId1 = <<"rule-debug-1">>,
- Rule1 = make_simple_rule(RuleId1),
- ok = create_rule(Rule1),
- ?assertMatch({ok, #{id := RuleId1}}, emqx_rule_engine:get_rule(RuleId1)),
- ok = delete_rule(Rule1),
- ?assertEqual(not_found, emqx_rule_engine:get_rule(RuleId1)),
- ok.
- t_add_get_remove_rules(_Config) ->
- delete_rules_by_ids([Id || #{id := Id} <- emqx_rule_engine:get_rules()]),
- ok = create_rules(
- [
- make_simple_rule(<<"rule-debug-1">>),
- make_simple_rule(<<"rule-debug-2">>)
- ]
- ),
- ?assertEqual(2, length(emqx_rule_engine:get_rules())),
- ok = delete_rules_by_ids([<<"rule-debug-1">>, <<"rule-debug-2">>]),
- ?assertEqual([], emqx_rule_engine:get_rules()),
- ok.
- t_create_existing_rule(_Config) ->
- %% create a rule using given rule id
- {ok, _} = emqx_rule_engine:create_rule(
- #{
- id => <<"an_existing_rule">>,
- sql => <<"select * from \"t/#\"">>,
- actions => [#{function => console}]
- }
- ),
- {ok, #{sql := SQL}} = emqx_rule_engine:get_rule(<<"an_existing_rule">>),
- ?assertEqual(<<"select * from \"t/#\"">>, SQL),
- ok = delete_rule(<<"an_existing_rule">>),
- ?assertEqual(not_found, emqx_rule_engine:get_rule(<<"an_existing_rule">>)),
- ok.
- t_get_rules_for_topic(_Config) ->
- Len0 = length(emqx_rule_engine:get_rules_for_topic(<<"simple/topic">>)),
- ok = create_rules(
- [
- make_simple_rule(<<"rule-debug-1">>),
- make_simple_rule(<<"rule-debug-2">>)
- ]
- ),
- ?assertEqual(Len0 + 2, length(emqx_rule_engine:get_rules_for_topic(<<"simple/topic">>))),
- ok = delete_rules_by_ids([<<"rule-debug-1">>, <<"rule-debug-2">>]),
- ok.
- t_get_rules_ordered_by_ts(_Config) ->
- Now = erlang:system_time(microsecond),
- ok = create_rules(
- [
- make_simple_rule_with_ts(<<"rule-debug-0">>, Now + 1),
- make_simple_rule_with_ts(<<"rule-debug-1">>, Now + 2),
- make_simple_rule_with_ts(<<"rule-debug-2">>, Now + 3)
- ]
- ),
- ?assertMatch(
- [
- #{id := <<"rule-debug-0">>},
- #{id := <<"rule-debug-1">>},
- #{id := <<"rule-debug-2">>}
- ],
- emqx_rule_engine:get_rules_ordered_by_ts()
- ).
- t_get_rules_for_topic_2(_Config) ->
- Len0 = length(emqx_rule_engine:get_rules_for_topic(<<"simple/1">>)),
- ok = create_rules(
- [
- make_simple_rule(<<"rule-debug-1">>, _1 = <<"select * from \"simple/#\"">>),
- make_simple_rule(<<"rule-debug-2">>, _2 = <<"select * from \"simple/+\"">>),
- make_simple_rule(<<"rule-debug-3">>, <<"select * from \"simple/+/1\"">>),
- make_simple_rule(<<"rule-debug-4">>, _3 = <<"select * from \"simple/1\"">>),
- make_simple_rule(
- <<"rule-debug-5">>,
- _4 = <<"select * from \"simple/2\", \"simple/+\", \"simple/3\"">>
- ),
- make_simple_rule(
- <<"rule-debug-6">>,
- <<"select * from \"simple/2\", \"simple/3\", \"simple/4\"">>
- )
- ]
- ),
- ?assertEqual(Len0 + 4, length(emqx_rule_engine:get_rules_for_topic(<<"simple/1">>))),
- ok = delete_rules_by_ids([
- <<"rule-debug-1">>,
- <<"rule-debug-2">>,
- <<"rule-debug-3">>,
- <<"rule-debug-4">>,
- <<"rule-debug-5">>,
- <<"rule-debug-6">>
- ]),
- ok.
- t_get_rules_with_same_event(_Config) ->
- PubT = <<"simple/1">>,
- PubN = length(emqx_rule_engine:get_rules_with_same_event(PubT)),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/client_connected">>)),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/client_disconnected">>)),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/session_subscribed">>)),
- ?assertEqual(
- [], emqx_rule_engine:get_rules_with_same_event(<<"$events/session_unsubscribed">>)
- ),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_delivered">>)),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_acked">>)),
- ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_dropped">>)),
- ok = create_rules(
- [
- make_simple_rule(<<"r1">>, <<"select * from \"simple/#\"">>),
- make_simple_rule(<<"r2">>, <<"select * from \"abc/+\"">>),
- make_simple_rule(
- <<"r3">>,
- <<"select * from \"$events/client_connected\"">>
- ),
- make_simple_rule(
- <<"r4">>,
- <<"select * from \"$events/client_disconnected\"">>
- ),
- make_simple_rule(
- <<"r5">>,
- <<"select * from \"$events/session_subscribed\"">>
- ),
- make_simple_rule(
- <<"r6">>,
- <<"select * from \"$events/session_unsubscribed\"">>
- ),
- make_simple_rule(
- <<"r7">>,
- <<"select * from \"$events/message_delivered\"">>
- ),
- make_simple_rule(
- <<"r8">>,
- <<"select * from \"$events/message_acked\"">>
- ),
- make_simple_rule(
- <<"r9">>,
- <<"select * from \"$events/message_dropped\"">>
- ),
- make_simple_rule(
- <<"r10">>,
- <<
- "select * from \"t/1\", "
- "\"$events/session_subscribed\", \"$events/client_connected\""
- >>
- )
- ]
- ),
- ?assertEqual(PubN + 3, length(emqx_rule_engine:get_rules_with_same_event(PubT))),
- ?assertEqual(
- 2, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/client_connected">>))
- ),
- ?assertEqual(
- 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/client_disconnected">>))
- ),
- ?assertEqual(
- 2, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/session_subscribed">>))
- ),
- ?assertEqual(
- 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/session_unsubscribed">>))
- ),
- ?assertEqual(
- 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_delivered">>))
- ),
- ?assertEqual(
- 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_acked">>))
- ),
- ?assertEqual(
- 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_dropped">>))
- ),
- ok = delete_rules_by_ids([
- <<"r1">>,
- <<"r2">>,
- <<"r3">>,
- <<"r4">>,
- <<"r5">>,
- <<"r6">>,
- <<"r7">>,
- <<"r8">>,
- <<"r9">>,
- <<"r10">>
- ]),
- ok.
- t_get_rule_ids_by_action(_) ->
- ID = <<"t_get_rule_ids_by_action">>,
- Rule1 = #{
- id => ID,
- sql => <<"SELECT * FROM \"t\"">>,
- actions => [
- #{function => console, args => #{}},
- #{function => republish, args => #{}},
- <<"mqtt:my_mqtt_bridge">>,
- <<"mysql:foo">>
- ],
- description => ID,
- created_at => erlang:system_time(millisecond)
- },
- ok = create_rules([Rule1]),
- ?assertMatch(
- [ID],
- emqx_rule_engine:get_rule_ids_by_action(#{function => <<"emqx_rule_actions:console">>})
- ),
- ?assertMatch(
- [ID],
- emqx_rule_engine:get_rule_ids_by_action(#{function => <<"emqx_rule_actions:republish">>})
- ),
- ?assertEqual([], emqx_rule_engine:get_rule_ids_by_action(#{function => <<"some_mod:fun">>})),
- ?assertMatch([ID], emqx_rule_engine:get_rule_ids_by_action(<<"mysql:foo">>)),
- ?assertEqual([], emqx_rule_engine:get_rule_ids_by_action(<<"mysql:not_exists">>)),
- ok = delete_rules_by_ids([<<"t_get_rule_ids_by_action">>]).
- t_ensure_action_removed(_) ->
- Id = <<"t_ensure_action_removed">>,
- GetSelectedData = <<"emqx_rule_sqltester:get_selected_data">>,
- emqx:update_config(
- [rule_engine, rules, Id],
- #{
- <<"actions">> => [
- #{<<"function">> => GetSelectedData},
- #{<<"function">> => <<"console">>},
- #{<<"function">> => <<"republish">>},
- <<"mysql:foo">>,
- <<"mqtt:bar">>
- ],
- <<"description">> => <<"">>,
- <<"sql">> => <<"SELECT * FROM \"t/#\"">>
- }
- ),
- ?assertMatch(
- #{
- <<"actions">> := [
- #{<<"function">> := GetSelectedData},
- #{<<"function">> := <<"console">>},
- #{<<"function">> := <<"republish">>},
- <<"mysql:foo">>,
- <<"mqtt:bar">>
- ]
- },
- emqx:get_raw_config([rule_engine, rules, Id])
- ),
- ok = emqx_rule_engine:ensure_action_removed(Id, #{function => <<"console">>}),
- ?assertMatch(
- #{
- <<"actions">> := [
- #{<<"function">> := GetSelectedData},
- #{<<"function">> := <<"republish">>},
- <<"mysql:foo">>,
- <<"mqtt:bar">>
- ]
- },
- emqx:get_raw_config([rule_engine, rules, Id])
- ),
- ok = emqx_rule_engine:ensure_action_removed(Id, <<"mysql:foo">>),
- ?assertMatch(
- #{
- <<"actions">> := [
- #{<<"function">> := GetSelectedData},
- #{<<"function">> := <<"republish">>},
- <<"mqtt:bar">>
- ]
- },
- emqx:get_raw_config([rule_engine, rules, Id])
- ),
- ok = emqx_rule_engine:ensure_action_removed(Id, #{function => GetSelectedData}),
- ?assertMatch(
- #{
- <<"actions">> := [
- #{<<"function">> := <<"republish">>},
- <<"mqtt:bar">>
- ]
- },
- emqx:get_raw_config([rule_engine, rules, Id])
- ),
- emqx:remove_config([rule_engine, rules, Id]).
- %%------------------------------------------------------------------------------
- %% Test cases for rule runtime
- %%------------------------------------------------------------------------------
- t_json_payload_decoding(_Config) ->
- {ok, C} = emqtt:start_link(),
- on_exit(fun() -> emqtt:stop(C) end),
- {ok, _} = emqtt:connect(C),
- Cases =
- [
- #{
- select_fields =>
- <<"payload.measurement, payload.data_type, payload.value, payload.device_id">>,
- payload => emqx_utils_json:encode(#{
- measurement => <<"temp">>,
- data_type => <<"FLOAT">>,
- value => <<"32.12">>,
- device_id => <<"devid">>
- }),
- expected => #{
- payload => #{
- <<"measurement">> => <<"temp">>,
- <<"data_type">> => <<"FLOAT">>,
- <<"value">> => <<"32.12">>,
- <<"device_id">> => <<"devid">>
- }
- }
- },
- %% "last write wins" examples
- #{
- select_fields => <<"payload as p, payload.f as p.answer">>,
- payload => emqx_utils_json:encode(#{f => 42, keep => <<"that?">>}),
- expected => #{
- <<"p">> => #{
- <<"answer">> => 42
- }
- }
- },
- #{
- select_fields => <<"payload as p, payload.f as p.jsonlike.f">>,
- payload => emqx_utils_json:encode(#{
- jsonlike => emqx_utils_json:encode(#{a => 0}),
- f => <<"huh">>
- }),
- %% behavior from 4.4: jsonlike gets wiped without preserving old "keys"
- %% here we overwrite it since we don't explicitly decode it
- expected => #{
- <<"p">> => #{
- <<"jsonlike">> => #{<<"f">> => <<"huh">>}
- }
- }
- },
- #{
- select_fields =>
- <<"payload as p, 42 as p, payload.measurement as p.measurement, 51 as p">>,
- payload => emqx_utils_json:encode(#{
- measurement => <<"temp">>,
- data_type => <<"FLOAT">>,
- value => <<"32.12">>,
- device_id => <<"devid">>
- }),
- expected => #{
- <<"p">> => 51
- }
- },
- %% if selected field is already structured, new values are inserted into it
- #{
- select_fields =>
- <<"json_decode(payload) as p, payload.a as p.z">>,
- payload => emqx_utils_json:encode(#{
- a => 1,
- b => <<"2">>
- }),
- expected => #{
- <<"p">> => #{
- <<"a">> => 1,
- <<"b">> => <<"2">>,
- <<"z">> => 1
- }
- }
- }
- ],
- ActionFn = <<(atom_to_binary(?MODULE))/binary, ":action_response">>,
- Topic = <<"some/topic">>,
- ok = snabbkaffe:start_trace(),
- on_exit(fun() -> snabbkaffe:stop() end),
- on_exit(fun() -> delete_rule(?TMP_RULEID) end),
- lists:foreach(
- fun(#{select_fields := Fs, payload := P, expected := E} = Case) ->
- ct:pal("testing case ~p", [Case]),
- SQL = <<"select ", Fs/binary, " from \"", Topic/binary, "\"">>,
- delete_rule(?TMP_RULEID),
- {ok, _Rule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [#{function => ActionFn}]
- }
- ),
- {_, {ok, Event}} =
- ?wait_async_action(
- emqtt:publish(C, Topic, P, 0),
- #{?snk_kind := action_response},
- 5_000
- ),
- ?assertMatch(
- #{selected := E},
- Event,
- #{payload => P, fields => Fs, expected => E}
- ),
- ok
- end,
- Cases
- ),
- snabbkaffe:stop(),
- ok.
- t_events(_Config) ->
- {ok, Client} = emqtt:start_link(
- [
- {username, <<"u_event">>},
- {clientid, <<"c_event">>},
- {proto_ver, v5},
- {properties, #{'Session-Expiry-Interval' => 60}}
- ]
- ),
- {ok, Client2} = emqtt:start_link(
- [
- {username, <<"u_event2">>},
- {clientid, <<"c_event2">>},
- {proto_ver, v5},
- {properties, #{'Session-Expiry-Interval' => 60}}
- ]
- ),
- ct:pal("====== verify $events/client_connected, $events/client_connack"),
- client_connected(Client, Client2),
- ct:pal("====== verify $events/message_dropped"),
- message_dropped(Client),
- ct:pal("====== verify $events/session_subscribed"),
- session_subscribed(Client2),
- ct:pal("====== verify t1"),
- message_publish(Client),
- ct:pal("====== verify $events/delivery_dropped"),
- delivery_dropped(Client),
- ct:pal("====== verify $events/message_delivered"),
- message_delivered(Client),
- ct:pal("====== verify $events/message_acked"),
- message_acked(Client),
- ct:pal("====== verify $events/session_unsubscribed"),
- session_unsubscribed(Client2),
- ct:pal("====== verify $events/client_disconnected"),
- client_disconnected(Client, Client2),
- ct:pal("====== verify $events/client_connack"),
- client_connack_failed(),
- ok.
- t_event_client_disconnected_normal(_Config) ->
- SQL =
- "select * "
- "from \"$events/client_disconnected\" ",
- RepubT = <<"repub/to/disconnected/normal">>,
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [republish_action(RepubT, <<>>)]
- }
- ),
- {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
- ct:sleep(200),
- {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client1),
- emqtt:disconnect(Client1),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(RepubT, T),
- ?assertMatch(
- #{<<"reason">> := <<"normal">>}, emqx_utils_json:decode(Payload, [return_maps])
- )
- after 1000 ->
- ct:fail(wait_for_repub_disconnected_normal)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_event_client_disconnected_kicked(_Config) ->
- SQL =
- "select * "
- "from \"$events/client_disconnected\" ",
- RepubT = <<"repub/to/disconnected/kicked">>,
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [republish_action(RepubT, <<>>)]
- }
- ),
- {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
- ct:sleep(200),
- {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client1),
- %% the process will receive {'EXIT',{shutdown,tcp_closed}}
- unlink(Client1),
- emqx_cm:kick_session(<<"emqx">>),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(RepubT, T),
- ?assertMatch(
- #{<<"reason">> := <<"kicked">>}, emqx_utils_json:decode(Payload, [return_maps])
- )
- after 1000 ->
- ct:fail(wait_for_repub_disconnected_kicked)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_event_client_disconnected_discarded(_Config) ->
- SQL =
- "select * "
- "from \"$events/client_disconnected\" ",
- RepubT = <<"repub/to/disconnected/discarded">>,
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [republish_action(RepubT, <<>>)]
- }
- ),
- {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
- ct:sleep(200),
- {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client1),
- %% the process will receive {'EXIT',{shutdown,tcp_closed}}
- unlink(Client1),
- {ok, Client2} = emqtt:start_link([
- {clientid, <<"emqx">>}, {username, <<"emqx">>}, {clean_start, true}
- ]),
- {ok, _} = emqtt:connect(Client2),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(RepubT, T),
- ?assertMatch(
- #{<<"reason">> := <<"discarded">>}, emqx_utils_json:decode(Payload, [return_maps])
- )
- after 1000 ->
- ct:fail(wait_for_repub_disconnected_discarded)
- end,
- emqtt:stop(Client),
- emqtt:stop(Client2),
- delete_rule(TopicRule).
- t_event_client_disconnected_takenover(_Config) ->
- SQL =
- "select * "
- "from \"$events/client_disconnected\" ",
- RepubT = <<"repub/to/disconnected/takenover">>,
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [republish_action(RepubT, <<>>)]
- }
- ),
- {ok, ClientRecv} = emqtt:start_link([
- {clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}
- ]),
- {ok, _} = emqtt:connect(ClientRecv),
- {ok, _, _} = emqtt:subscribe(ClientRecv, RepubT, 0),
- ct:sleep(200),
- {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client1),
- %% the process will receive {'EXIT',{shutdown,tcp_closed}}
- unlink(Client1),
- {ok, Client2} = emqtt:start_link([
- {clientid, <<"emqx">>}, {username, <<"emqx">>}, {clean_start, false}
- ]),
- {ok, _} = emqtt:connect(Client2),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(RepubT, T),
- ?assertMatch(
- #{<<"reason">> := <<"takenover">>}, emqx_utils_json:decode(Payload, [return_maps])
- )
- after 1000 ->
- ct:fail(wait_for_repub_disconnected_discarded)
- end,
- emqtt:stop(ClientRecv),
- emqtt:stop(Client2),
- delete_rule(TopicRule).
- client_connack_failed() ->
- {ok, Client} = emqtt:start_link(
- [
- {username, <<"u_event3">>},
- {clientid, <<"c_event3">>},
- {proto_ver, v5},
- {properties, #{'Session-Expiry-Interval' => 60}}
- ]
- ),
- try
- meck:new(emqx_access_control, [non_strict, passthrough]),
- meck:expect(
- emqx_access_control,
- authenticate,
- fun(_) -> {error, bad_username_or_password} end
- ),
- process_flag(trap_exit, true),
- ?assertMatch({error, _}, emqtt:connect(Client)),
- timer:sleep(300),
- verify_event('client.connack')
- after
- meck:unload(emqx_access_control)
- end,
- ok.
- message_publish(Client) ->
- emqtt:publish(
- Client,
- <<"t1">>,
- #{'Message-Expiry-Interval' => 60},
- <<"{\"id\": 1, \"name\": \"ha\"}">>,
- [{qos, 1}]
- ),
- verify_event('message.publish'),
- ok.
- client_connected(Client, Client2) ->
- {ok, _} = emqtt:connect(Client),
- {ok, _} = emqtt:connect(Client2),
- verify_event('client.connack'),
- verify_event('client.connected'),
- ok.
- client_disconnected(Client, Client2) ->
- ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
- ok = emqtt:disconnect(Client2, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
- verify_event('client.disconnected'),
- ok.
- session_subscribed(Client2) ->
- {ok, _, _} = emqtt:subscribe(
- Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>, 1
- ),
- verify_event('session.subscribed'),
- verify_event('client.check_authz_complete'),
- ok.
- session_unsubscribed(Client2) ->
- {ok, _, _} = emqtt:unsubscribe(
- Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>
- ),
- verify_event('session.unsubscribed'),
- ok.
- message_delivered(_Client) ->
- verify_event('message.delivered'),
- ok.
- delivery_dropped(Client) ->
- %% subscribe "t1" and then publish to "t1", the message will not be received by itself
- %% because we have set the subscribe flag 'nl' = true
- {ok, _, _} = emqtt:subscribe(Client, #{}, <<"t1">>, [{nl, true}, {qos, 1}]),
- ct:sleep(50),
- message_publish(Client),
- ct:pal("--- current emqx hooks: ~p", [ets:tab2list(emqx_hooks)]),
- verify_event('delivery.dropped'),
- ok.
- message_dropped(Client) ->
- message_publish(Client),
- verify_event('message.dropped'),
- ok.
- message_acked(_Client) ->
- verify_event('message.acked'),
- ok.
- t_match_atom_and_binary(_Config) ->
- SQL =
- "SELECT connected_at as ts, * "
- "FROM \"$events/client_connected\" "
- "WHERE username = 'emqx2' ",
- Repub = republish_action(<<"t2">>, <<"user:${ts}">>),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client} = emqtt:start_link([{username, <<"emqx1">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- ct:sleep(100),
- {ok, Client2} = emqtt:start_link([{username, <<"emqx2">>}]),
- {ok, _} = emqtt:connect(Client2),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(<<"t2">>, T),
- <<"user:", ConnAt/binary>> = Payload,
- _ = binary_to_integer(ConnAt)
- after 1000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_sqlselect_0(_Config) ->
- %% Verify SELECT with and without 'AS'
- Sql =
- "select * "
- "from \"t/#\" "
- "where payload.cmd.info = 'tt'",
- ?assertMatch(
- {ok, #{payload := <<"{\"cmd\": {\"info\":\"tt\"}}">>}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload =>
- <<"{\"cmd\": {\"info\":\"tt\"}}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "select payload.cmd as cmd "
- "from \"t/#\" "
- "where cmd.info = 'tt'",
- ?assertMatch(
- {ok, #{<<"cmd">> := #{<<"info">> := <<"tt">>}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload =>
- <<"{\"cmd\": {\"info\":\"tt\"}}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql3 =
- "select payload.cmd as cmd, cmd.info as info "
- "from \"t/#\" "
- "where cmd.info = 'tt' and info = 'tt'",
- ?assertMatch(
- {ok, #{
- <<"cmd">> := #{<<"info">> := <<"tt">>},
- <<"info">> := <<"tt">>
- }},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context =>
- #{
- payload =>
- <<"{\"cmd\": {\"info\":\"tt\"}}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% cascaded as
- Sql4 =
- "select payload.cmd as cmd, cmd.info as meta.info "
- "from \"t/#\" "
- "where cmd.info = 'tt' and meta.info = 'tt'",
- ?assertMatch(
- {ok, #{
- <<"cmd">> := #{<<"info">> := <<"tt">>},
- <<"meta">> := #{<<"info">> := <<"tt">>}
- }},
- emqx_rule_sqltester:test(
- #{
- sql => Sql4,
- context =>
- #{
- payload =>
- <<"{\"cmd\": {\"info\":\"tt\"}}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlselect_00(_Config) ->
- %% Verify plus/subtract and unary_add_or_subtract
- Sql =
- "select 1-1 as a "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql1 =
- "select -1 + 1 as a "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context =>
- #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "select 1 + 1 as a "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := 2}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql3 =
- "select +1 as a "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := 1}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context =>
- #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlselect_with_3rd_party_impl(_Config) ->
- Sql =
- "select * from \"t/#\" where emqx_rule_funcs_demo.is_my_topic(topic)",
- T = fun(Topic) ->
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => #{<<"what">> => 0},
- topic => Topic
- }
- }
- )
- end,
- ?assertMatch({ok, _}, T(<<"t/2/3/4/5">>)),
- ?assertMatch({error, nomatch}, T(<<"t/1">>)).
- t_sqlselect_with_3rd_party_impl2(_Config) ->
- Sql = fun(N) ->
- "select emqx_rule_funcs_demo.duplicate_payload(payload," ++ integer_to_list(N) ++
- ") as payload_list from \"t/#\""
- end,
- T = fun(Payload, N) ->
- emqx_rule_sqltester:test(
- #{
- sql => Sql(N),
- context =>
- #{
- payload => Payload,
- topic => <<"t/a">>
- }
- }
- )
- end,
- ?assertMatch({ok, #{<<"payload_list">> := [_, _]}}, T(<<"payload1">>, 2)),
- ?assertMatch({ok, #{<<"payload_list">> := [_, _, _]}}, T(<<"payload1">>, 3)),
- %% crash
- ?assertMatch({error, {select_and_transform_error, _}}, T(<<"payload1">>, 4)).
- t_sqlselect_with_3rd_party_funcs_unknown(_Config) ->
- Sql = "select emqx_rule_funcs_demo_no_such_module.foo(payload) from \"t/#\"",
- ?assertMatch(
- {error,
- {select_and_transform_error,
- {throw, #{reason := sql_function_provider_module_not_loaded}, _}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{payload => <<"a">>, topic => <<"t/a">>}
- }
- )
- ).
- t_sqlselect_001(_Config) ->
- %% Verify that the jq function can be called from SQL
- Sql =
- "select jq('.what + .what', payload) as ans "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"ans">> := [8]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => #{<<"what">> => 4},
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "SELECT jq('.a|.[]', "
- "'{\"a\": [{\"b\": 1}, {\"b\": 2}, {\"b\": 3}]}') "
- "as jq_action, "
- " jq_action[1].b as first_b from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"first_b">> := 1}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload => #{<<"what">> => 4},
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlselect_002(_Config) ->
- %% Verify that the div and mod can be used both as infix operations and as
- %% function calls
- Sql =
- ""
- "select 2 mod 2 as mod1,\n"
- " mod(3, 2) as mod2,\n"
- " 4 div 2 as div1,\n"
- " div(7, 2) as div2\n"
- " from \"t/#\" "
- "",
- ?assertMatch(
- {ok, #{
- <<"mod1">> := 0,
- <<"mod2">> := 1,
- <<"div1">> := 2,
- <<"div2">> := 3
- }},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => #{<<"what">> => 4},
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlselect_inject_props(_Config) ->
- SQL =
- "SELECT json_decode(payload) as p, payload, "
- "map_put('inject_key', 'inject_val', user_properties) as user_properties "
- "FROM \"t3/#\", \"t1\" "
- "WHERE p.x = 1",
- Repub = republish_action(<<"t2">>),
- {ok, TopicRule1} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- Props = user_properties(#{<<"inject_key">> => <<"inject_val">>}),
- {ok, Client} = emqtt:start_link([{username, <<"emqx">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- emqtt:publish(Client, <<"t1">>, #{}, <<"{\"x\":1}">>, [{qos, 0}]),
- receive
- {publish, #{topic := T, payload := Payload, properties := Props2}} ->
- ?assertEqual(Props, Props2),
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"{\"x\":1}">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule1).
- t_sqlselect_01(_Config) ->
- SQL =
- "SELECT json_decode(payload) as p, payload "
- "FROM \"t3/#\", \"t1\" "
- "WHERE p.x = 1",
- Repub = republish_action(<<"t2">>, <<"${payload}">>, <<"${pub_props.'User-Property'}">>),
- {ok, TopicRule1} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- Props = user_properties(#{<<"mykey">> => <<"myval">>}),
- {ok, Client} = emqtt:start_link([{username, <<"emqx">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- emqtt:publish(Client, <<"t1">>, Props, <<"{\"x\":1}">>, [{qos, 0}]),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"{\"x\":1}">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:publish(Client, <<"t1">>, Props, <<"{\"x\":2}">>, [{qos, 0}]),
- receive
- {publish, #{topic := <<"t2">>, payload := _}} ->
- ct:fail(unexpected_t2)
- after 2000 ->
- ok
- end,
- emqtt:publish(Client, <<"t3/a">>, Props, <<"{\"x\":1}">>, [{qos, 0}]),
- receive
- {publish, #{topic := T3, payload := Payload3, properties := Props2}} ->
- ?assertEqual(Props, Props2),
- ?assertEqual(<<"t2">>, T3),
- ?assertEqual(<<"{\"x\":1}">>, Payload3)
- after 2000 ->
- ct:fail(wait_for_t3)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule1).
- t_sqlselect_02(_Config) ->
- SQL =
- "SELECT * "
- "FROM \"t3/#\", \"t1\" "
- "WHERE payload.x = 1",
- Repub = republish_action(<<"t2">>),
- {ok, TopicRule1} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, 0),
- ct:sleep(100),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"{\"x\":1}">>, Payload)
- after 1000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:publish(Client, <<"t1">>, <<"{\"x\":2}">>, 0),
- receive
- {publish, #{topic := <<"t2">>, payload := Payload0}} ->
- ct:fail({unexpected_t2, Payload0})
- after 1000 ->
- ok
- end,
- emqtt:publish(Client, <<"t3/a">>, <<"{\"x\":1}">>, 0),
- receive
- {publish, #{topic := T3, payload := Payload3}} ->
- ?assertEqual(<<"t2">>, T3),
- ?assertEqual(<<"{\"x\":1}">>, Payload3)
- after 1000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule1).
- t_sqlselect_1(_Config) ->
- SQL =
- "SELECT json_decode(payload) as p, payload "
- "FROM \"t1\" "
- "WHERE p.x = 1 and p.y = 2",
- Repub = republish_action(<<"t2">>),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":2}">>, 0),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"{\"x\":1,\"y\":2}">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":1}">>, 0),
- receive
- {publish, #{topic := <<"t2">>, payload := _}} ->
- ct:fail(unexpected_t2)
- after 1000 ->
- ok
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_sqlselect_2(_Config) ->
- %% recursively republish to t2
- SQL = "SELECT * FROM \"t2\" ",
- Repub = republish_action(<<"t2">>),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- emqtt:publish(Client, <<"t2">>, <<"{\"x\":1,\"y\":144}">>, 0),
- Fun = fun() ->
- receive
- {publish, #{topic := <<"t2">>, payload := _}} ->
- received_t2
- after 500 ->
- received_nothing
- end
- end,
- received_t2 = Fun(),
- received_t2 = Fun(),
- received_nothing = Fun(),
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_sqlselect_3(_Config) ->
- %% republish the client.connected msg
- SQL =
- "SELECT * "
- "FROM \"$events/client_connected\" "
- "WHERE username = 'emqx1'",
- Repub = republish_action(<<"t2">>, <<"clientid=${clientid}">>),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client} = emqtt:start_link([{clientid, <<"emqx0">>}, {username, <<"emqx0">>}]),
- {ok, _} = emqtt:connect(Client),
- {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
- {ok, Client1} = emqtt:start_link([{clientid, <<"c_emqx1">>}, {username, <<"emqx1">>}]),
- {ok, _} = emqtt:connect(Client1),
- receive
- {publish, #{topic := T, payload := Payload}} ->
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"clientid=c_emqx1">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":1}">>, 0),
- receive
- {publish, #{topic := <<"t2">>, payload := _}} ->
- ct:fail(unexpected_t2)
- after 1000 ->
- ok
- end,
- emqtt:stop(Client),
- delete_rule(TopicRule).
- t_sqlselect_message_publish_event_keep_original_props_1(_Config) ->
- %% republish the client.connected msg
- Topic = <<"foo/bar/1">>,
- SQL = <<
- "SELECT clientid "
- "FROM \"$events/message_dropped\" "
- >>,
- %"WHERE topic = \"", Topic/binary, "\"">>,
- Repub = republish_action(
- <<"t2">>,
- <<"clientid=${clientid}">>,
- <<"${pub_props.'User-Property'}">>
- ),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client1} = emqtt:start_link([{clientid, <<"sub-01">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client1),
- {ok, _, _} = emqtt:subscribe(Client1, <<"t2">>, 1),
- {ok, Client2} = emqtt:start_link([{clientid, <<"pub-02">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client2),
- Props = user_properties(#{<<"mykey">> => <<"111111">>}),
- emqtt:publish(Client2, Topic, Props, <<"{\"x\":1}">>, [{qos, 1}]),
- receive
- {publish, #{topic := T, payload := Payload, properties := Props1}} ->
- ?assertEqual(Props1, Props),
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"clientid=pub-02">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:stop(Client2),
- emqtt:stop(Client1),
- delete_rule(TopicRule).
- t_sqlselect_message_publish_event_keep_original_props_2(_Config) ->
- %% republish the client.connected msg
- Topic = <<"foo/bar/1">>,
- SQL = <<
- "SELECT clientid, pub_props.'User-Property' as user_properties "
- "FROM \"$events/message_dropped\" "
- >>,
- %"WHERE topic = \"", Topic/binary, "\"">>,
- Repub = republish_action(<<"t2">>, <<"clientid=${clientid}">>),
- {ok, TopicRule} = emqx_rule_engine:create_rule(
- #{
- sql => SQL,
- id => ?TMP_RULEID,
- actions => [Repub]
- }
- ),
- {ok, Client1} = emqtt:start_link([{clientid, <<"sub-01">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client1),
- {ok, _, _} = emqtt:subscribe(Client1, <<"t2">>, 1),
- {ok, Client2} = emqtt:start_link([{clientid, <<"pub-02">>}, {proto_ver, v5}]),
- {ok, _} = emqtt:connect(Client2),
- Props = user_properties(#{<<"mykey">> => <<"222222222222">>}),
- emqtt:publish(Client2, Topic, Props, <<"{\"x\":1}">>, [{qos, 1}]),
- receive
- {publish, #{topic := T, payload := Payload, properties := Props1}} ->
- ?assertEqual(Props1, Props),
- ?assertEqual(<<"t2">>, T),
- ?assertEqual(<<"clientid=pub-02">>, Payload)
- after 2000 ->
- ct:fail(wait_for_t2)
- end,
- emqtt:stop(Client2),
- emqtt:stop(Client1),
- delete_rule(TopicRule).
- t_sqlparse_event_1(_Config) ->
- Sql =
- "select topic as tp "
- "from \"$events/session_subscribed\" ",
- ?assertMatch(
- {ok, #{<<"tp">> := <<"t/tt">>}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{topic => <<"t/tt">>}
- }
- )
- ).
- t_sqlparse_event_2(_Config) ->
- Sql =
- "select clientid "
- "from \"$events/client_connected\" ",
- ?assertMatch(
- {ok, #{<<"clientid">> := <<"abc">>}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{clientid => <<"abc">>}
- }
- )
- ).
- t_sqlparse_event_3(_Config) ->
- Sql =
- "select clientid, topic as tp "
- "from \"t/tt\", \"$events/client_connected\" ",
- ?assertMatch(
- {ok, #{<<"clientid">> := <<"abc">>, <<"tp">> := <<"t/tt">>}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{clientid => <<"abc">>, topic => <<"t/tt">>}
- }
- )
- ).
- t_sqlparse_foreach_1(_Config) ->
- %% Verify foreach with and without 'AS'
- Sql =
- "foreach payload.sensors as s "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{<<"s">> := 1}, #{<<"s">> := 2}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{item := 1}, #{item := 2}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context => #{
- payload => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql3 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [
- #{item := #{<<"cmd">> := <<"1">>}, clientid := <<"c_a">>},
- #{item := #{<<"cmd">> := <<"2">>, <<"name">> := <<"ct">>}, clientid := <<"c_a">>}
- ]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context => #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\",\"name\":\"ct\"}]}">>,
- clientid => <<"c_a">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql4 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- {ok, [
- #{metadata := #{rule_id := TRuleId}},
- #{metadata := #{rule_id := TRuleId}}
- ]} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql4,
- context => #{
- payload => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- ),
- Sql5 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- {ok, [
- #{payload := #{<<"sensors">> := _}},
- #{payload := #{<<"sensors">> := _}}
- ]} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql5,
- context => #{
- payload => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- ),
- try
- meck:new(emqx_rule_runtime, [non_strict, passthrough]),
- meck:expect(
- emqx_rule_runtime,
- apply_rule,
- fun(Rule, #{payload := Payload} = Columns, Env) ->
- Columns2 = maps:put(<<"payload">>, Payload, maps:without([payload], Columns)),
- meck:passthrough([Rule, Columns2, Env])
- end
- ),
- Sql6 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- {ok, [
- #{<<"payload">> := #{<<"sensors">> := _}},
- #{<<"payload">> := #{<<"sensors">> := _}}
- ]} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql6,
- context => #{
- <<"payload">> => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- ),
- Sql7 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- ?assertNotMatch(
- {ok, [
- #{<<"payload">> := _, payload := _},
- #{<<"payload">> := _, payload := _}
- ]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql7,
- context => #{
- <<"payload">> => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- )
- )
- after
- meck:unload(emqx_rule_runtime)
- end,
- ?assert(is_binary(TRuleId)).
- t_sqlparse_foreach_2(_Config) ->
- %% Verify foreach-do with and without 'AS'
- Sql =
- "foreach payload.sensors as s "
- "do s.cmd as msg_type "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach payload.sensors "
- "do item.cmd as msg_type "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql3 =
- "foreach payload.sensors "
- "do item as item "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{<<"item">> := 1}, #{<<"item">> := 2}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_foreach_3(_Config) ->
- %% Verify foreach-incase with and without 'AS'
- Sql =
- "foreach payload.sensors as s "
- "incase s.cmd != 1 "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [
- #{<<"s">> := #{<<"cmd">> := 2}},
- #{<<"s">> := #{<<"cmd">> := 3}}
- ]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":1}, {\"cmd\":2}, {\"cmd\":3}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach payload.sensors "
- "incase item.cmd != 1 "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [
- #{item := #{<<"cmd">> := 2}},
- #{item := #{<<"cmd">> := 3}}
- ]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":1}, {\"cmd\":2}, {\"cmd\":3}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_foreach_4(_Config) ->
- %% Verify foreach-do-incase
- Sql =
- "foreach payload.sensors as s "
- "do s.cmd as msg_type, s.name as name "
- "incase is_not_null(s.cmd) "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, [#{<<"msg_type">> := <<"1">>, <<"name">> := <<"n1">>}, #{<<"msg_type">> := <<"2">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\", \"name\":\"n1\"}, {\"cmd\":\"2\"}, {\"name\":\"n3\"}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, []},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => <<"{\"sensors\": [1, 2]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_foreach_5(_Config) ->
- %% Verify foreach on a empty-list or non-list variable
- Sql =
- "foreach payload.sensors as s "
- "do s.cmd as msg_type, s.name as name "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, []},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => <<"{\"sensors\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, []},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => <<"{\"sensors\": []}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach payload.sensors "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, []},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload => <<"{\"sensors\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_foreach_6(_Config) ->
- %% Verify foreach on a empty-list or non-list variable
- Sql =
- "foreach json_decode(payload) "
- "do item.id as zid, timestamp as t "
- "from \"t/#\" ",
- {ok, Res} = emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => <<"[{\"id\": 5},{\"id\": 15}]">>,
- topic => <<"t/a">>
- }
- }
- ),
- [
- #{<<"t">> := Ts1, <<"zid">> := Zid1},
- #{<<"t">> := Ts2, <<"zid">> := Zid2}
- ] = Res,
- ?assertEqual(true, is_integer(Ts1)),
- ?assertEqual(true, is_integer(Ts2)),
- ?assert(Zid1 == 5 orelse Zid1 == 15),
- ?assert(Zid2 == 5 orelse Zid2 == 15).
- t_sqlparse_foreach_7(_Config) ->
- %% Verify foreach-do-incase and cascaded AS
- Sql =
- "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
- "do info.cmd as msg_type, info.name as name "
- "incase is_not_null(info.cmd) "
- "from \"t/#\" "
- "where s.page = '2' ",
- Payload = <<
- "{\"sensors\": {\"page\": 2, \"collection\": "
- "{\"info\":[{\"name\":\"cmd1\", \"cmd\":\"1\"}, {\"cmd\":\"2\"}]} } }"
- >>,
- ?assertMatch(
- {ok, [#{<<"name">> := <<"cmd1">>, <<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => Payload,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
- "do info.cmd as msg_type, info.name as name "
- "incase is_not_null(info.cmd) "
- "from \"t/#\" "
- "where s.page = '3' ",
- ?assertMatch(
- {error, nomatch},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload => Payload,
- topic => <<"t/a">>
- }
- }
- )
- ).
- -define(COLL, #{<<"info">> := [<<"haha">>, #{<<"name">> := <<"cmd1">>, <<"cmd">> := <<"1">>}]}).
- t_sqlparse_foreach_8(_Config) ->
- %% Verify foreach-do-incase and cascaded AS
- Sql =
- "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
- "do info.cmd as msg_type, info.name as name, s, c "
- "incase is_map(info) "
- "from \"t/#\" "
- "where s.page = '2' ",
- Payload = <<
- "{\"sensors\": {\"page\": 2, \"collection\": "
- "{\"info\":[\"haha\", {\"name\":\"cmd1\", \"cmd\":\"1\"}]} } }"
- >>,
- ?assertMatch(
- {ok, [
- #{
- <<"name">> := <<"cmd1">>,
- <<"msg_type">> := <<"1">>,
- <<"s">> := #{<<"page">> := 2, <<"collection">> := ?COLL},
- <<"c">> := ?COLL
- }
- ]},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context =>
- #{
- payload => Payload,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql3 =
- "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, sublist(2,1,c.info) as info "
- "do info.cmd as msg_type, info.name as name "
- "from \"t/#\" "
- "where s.page = '2' ",
- [
- ?assertMatch(
- {ok, [#{<<"name">> := <<"cmd1">>, <<"msg_type">> := <<"1">>}]},
- emqx_rule_sqltester:test(
- #{
- sql => SqlN,
- context =>
- #{
- payload => Payload,
- topic => <<"t/a">>
- }
- }
- )
- )
- || SqlN <- [Sql3]
- ].
- t_sqlparse_case_when_1(_Config) ->
- %% case-when-else clause
- Sql =
- "select "
- " case when payload.x < 0 then 0 "
- " when payload.x > 7 then 7 "
- " else payload.x "
- " end as y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"y">> := 1}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 0}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": -1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 7}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 7}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 7}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 8}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ok.
- t_sqlparse_case_when_2(_Config) ->
- % switch clause
- Sql =
- "select "
- " case payload.x when 1 then 2 "
- " when 2 then 3 "
- " else 4 "
- " end as y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"y">> := 2}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 3}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 2}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 4}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 4}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 4}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 7}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 4}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 8}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_case_when_3(_Config) ->
- %% case-when clause
- Sql =
- "select "
- " case when payload.x < 0 then 0 "
- " when payload.x > 7 then 7 "
- " end as y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 5}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 0}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": -1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 7}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{<<"y">> := 7}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 8}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ok.
- t_sqlparse_array_index_1(_Config) ->
- %% index get
- Sql =
- "select "
- " json_decode(payload) as p, "
- " p[1] as a "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := #{<<"x">> := 1}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"[{\"x\": 1}]">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- ?assertMatch(
- {ok, #{}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- payload => <<"{\"x\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% index get without 'as'
- Sql2 =
- "select "
- " payload.x[2] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{payload := #{<<"x">> := [3]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context => #{
- payload => #{<<"x">> => [1, 3, 4]},
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% index get without 'as' again
- Sql3 =
- "select "
- " payload.x[2].y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{payload := #{<<"x">> := [#{<<"y">> := 3}]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context => #{
- payload => #{<<"x">> => [1, #{y => 3}, 4]},
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% index get with 'as'
- Sql4 =
- "select "
- " payload.x[2].y as b "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"b">> := 3}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql4,
- context => #{
- payload => #{<<"x">> => [1, #{y => 3}, 4]},
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_index_2(_Config) ->
- %% array get with negative index
- Sql1 =
- "select "
- " payload.x[-2].y as b "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"b">> := 3}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context => #{
- payload => #{<<"x">> => [1, #{y => 3}, 4]},
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% array append to head or tail of a list:
- Sql2 =
- "select "
- " payload.x as b, "
- " 1 as c[-0], "
- " 2 as c[-0], "
- " b as c[0] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"b">> := 0, <<"c">> := [0, 1, 2]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context => #{
- payload => #{<<"x">> => 0},
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% construct an empty list:
- Sql3 =
- "select "
- " [] as c, "
- " 1 as c[-0], "
- " 2 as c[-0], "
- " 0 as c[0] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"c">> := [0, 1, 2]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql3,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% construct a list:
- Sql4 =
- "select "
- " [payload.a, \"topic\", 'c'] as c, "
- " 1 as c[-0], "
- " 2 as c[-0], "
- " 0 as c[0] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"c">> := [0, 11, <<"t/a">>, <<"c">>, 1, 2]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql4,
- context => #{
- payload => <<"{\"a\":11}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_index_3(_Config) ->
- %% array with json string payload:
- Sql0 =
- "select "
- "payload,"
- "payload.x[2].y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"payload">> := #{<<"x">> := [1, #{<<"y">> := [1, 2]}, 3]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql0,
- context => #{
- payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% same as above but don't select payload:
- Sql1 =
- "select "
- "payload.x[2].y as b "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"b">> := [1, 2]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context => #{
- payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% same as above but add 'as' clause:
- Sql2 =
- "select "
- "payload.x[2].y as b.c "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"b">> := #{<<"c">> := [1, 2]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context => #{
- payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_index_4(_Config) ->
- %% array with json string payload:
- Sql0 =
- "select "
- "0 as payload.x[2].y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"payload">> := #{<<"x">> := [#{<<"y">> := 0}]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql0,
- context => #{
- payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% array with json string payload, and also select payload.x:
- Sql1 =
- "select "
- "payload.x, "
- "0 as payload.x[2].y "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{payload := #{<<"x">> := [1, #{<<"y">> := 0}, 3]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context => #{
- payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_index_5(_Config) ->
- Sql00 =
- "select "
- " [1,2,3,4] "
- "from \"t/#\" ",
- {ok, Res00} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- ),
- ?assert(
- lists:any(
- fun({_K, V}) ->
- V =:= [1, 2, 3, 4]
- end,
- maps:to_list(Res00)
- )
- ).
- t_sqlparse_select_matadata_1(_Config) ->
- %% array with json string payload:
- Sql0 =
- "select "
- "payload "
- "from \"t/#\" ",
- ?assertNotMatch(
- {ok, #{<<"payload">> := <<"abc">>, metadata := _}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql0,
- context => #{
- payload => <<"abc">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql1 =
- "select "
- "payload, metadata "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"payload">> := <<"abc">>, <<"metadata">> := _}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context => #{
- payload => <<"abc">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_range_1(_Config) ->
- %% get a range of list
- Sql0 =
- "select "
- " payload.a[1..4] as c "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"c">> := [0, 1, 2, 3]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql0,
- context => #{
- payload => <<"{\"a\":[0,1,2,3,4,5]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% get a range from non-list data
- Sql02 =
- "select "
- " payload.a[1..4] as c "
- "from \"t/#\" ",
- ?assertMatch(
- {error, {select_and_transform_error, {error, {range_get, non_list_data}, _}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql02,
- context =>
- #{
- payload => <<"{\"x\":[0,1,2,3,4,5]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% construct a range:
- Sql1 =
- "select "
- " [1..4] as c, "
- " 5 as c[-0], "
- " 6 as c[-0], "
- " 0 as c[0] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"c">> := [0, 1, 2, 3, 4, 5, 6]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql1,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_array_range_2(_Config) ->
- %% construct a range without 'as'
- Sql00 =
- "select "
- " [1..4] "
- "from \"t/#\" ",
- {ok, Res00} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- ),
- ?assert(
- lists:any(
- fun({_K, V}) ->
- V =:= [1, 2, 3, 4]
- end,
- maps:to_list(Res00)
- )
- ),
- %% construct a range without 'as'
- Sql01 =
- "select "
- " a[2..4] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"a">> := [2, 3, 4]}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql01,
- context => #{
- <<"a">> => [1, 2, 3, 4, 5],
- topic => <<"t/a">>
- }
- }
- )
- ),
- %% get a range of list without 'as'
- Sql02 =
- "select "
- " payload.a[1..4] "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{payload := #{<<"a">> := [0, 1, 2, 3]}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql02,
- context => #{
- payload => <<"{\"a\":[0,1,2,3,4,5]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_true_false(_Config) ->
- %% construct a range without 'as'
- Sql00 =
- "select "
- " true as a, false as b, "
- " false as x.y, true as c[-0] "
- "from \"t/#\" ",
- {ok, Res00} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- ),
- ?assertMatch(
- #{
- <<"a">> := true,
- <<"b">> := false,
- <<"x">> := #{<<"y">> := false},
- <<"c">> := [true]
- },
- Res00
- ).
- t_sqlparse_undefined_variable(_Config) ->
- %% undefined == undefined
- Sql00 =
- "select "
- "a, b "
- "from \"t/#\" "
- "where a = b",
- {ok, Res00} = emqx_rule_sqltester:test(
- #{sql => Sql00, context => #{payload => <<"">>, topic => <<"t/a">>}}
- ),
- ?assertEqual(#{<<"a">> => undefined, <<"b">> => undefined}, Res00),
- ?assertEqual(2, map_size(Res00)),
- %% undefined compare to non-undefined variables should return false
- Sql01 =
- "select "
- "a, b "
- "from \"t/#\" "
- "where a > b",
- {error, nomatch} = emqx_rule_sqltester:test(
- #{
- sql => Sql01,
- context => #{payload => <<"{\"b\":1}">>, topic => <<"t/a">>}
- }
- ),
- Sql02 =
- "select "
- "a < b as c "
- "from \"t/#\" ",
- {ok, Res02} = emqx_rule_sqltester:test(
- #{
- sql => Sql02,
- context => #{payload => <<"{\"b\":1}">>, topic => <<"t/a">>}
- }
- ),
- ?assertMatch(#{<<"c">> := false}, Res02).
- t_sqlparse_new_map(_Config) ->
- %% construct a range without 'as'
- Sql00 =
- "select "
- " map_new() as a, map_new() as b, "
- " map_new() as x.y, map_new() as c[-0] "
- "from \"t/#\" ",
- {ok, Res00} =
- emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => <<"">>,
- topic => <<"t/a">>
- }
- }
- ),
- ?assertMatch(
- #{
- <<"a">> := #{},
- <<"b">> := #{},
- <<"x">> := #{<<"y">> := #{}},
- <<"c">> := [#{}]
- },
- Res00
- ).
- t_sqlparse_payload_as(_Config) ->
- %% https://github.com/emqx/emqx/issues/3866
- Sql00 =
- "SELECT "
- " payload, map_get('engineWorkTime', payload.params, -1) as payload.params.engineWorkTime, "
- " map_get('hydOilTem', payload.params, -1) as payload.params.hydOilTem "
- "FROM \"t/#\" ",
- Payload1 =
- <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42, \"hydOilTem\": 30 } }">>,
- {ok, Res01} = emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => Payload1,
- topic => <<"t/a">>
- }
- }
- ),
- ?assertMatch(
- #{
- <<"payload">> := #{
- <<"params">> := #{
- <<"convertTemp">> := 20,
- <<"engineSpeed">> := 42,
- <<"engineWorkTime">> := -1,
- <<"hydOilTem">> := 30
- }
- }
- },
- Res01
- ),
- Payload2 = <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42 } }">>,
- {ok, Res02} = emqx_rule_sqltester:test(
- #{
- sql => Sql00,
- context => #{
- payload => Payload2,
- topic => <<"t/a">>
- }
- }
- ),
- ?assertMatch(
- #{
- <<"payload">> := #{
- <<"params">> := #{
- <<"convertTemp">> := 20,
- <<"engineSpeed">> := 42,
- <<"engineWorkTime">> := -1,
- <<"hydOilTem">> := -1
- }
- }
- },
- Res02
- ).
- t_sqlparse_nested_get(_Config) ->
- Sql =
- "select payload as p, p.a.b as c "
- "from \"t/#\" ",
- ?assertMatch(
- {ok, #{<<"c">> := 0}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql,
- context => #{
- topic => <<"t/1">>,
- payload => <<"{\"a\": {\"b\": 0}}">>
- }
- }
- )
- ).
- t_sqlparse_invalid_json(_Config) ->
- Sql02 =
- "select "
- " payload.a[1..4] as c "
- "from \"t/#\" ",
- ?assertMatch(
- {error, {select_and_transform_error, {error, {decode_json_failed, _}, _}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql02,
- context =>
- #{
- payload => <<"{\"x\":[0,1,2,3,}">>,
- topic => <<"t/a">>
- }
- }
- )
- ),
- Sql2 =
- "foreach payload.sensors "
- "do item.cmd as msg_type "
- "from \"t/#\" ",
- ?assertMatch(
- {error, {select_and_collect_error, {error, {decode_json_failed, _}, _}}},
- emqx_rule_sqltester:test(
- #{
- sql => Sql2,
- context =>
- #{
- payload =>
- <<"{\"sensors\": [{\"cmd\":\"1\"} {\"cmd\":}]}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- t_sqlparse_both_string_types_in_from(_Config) ->
- %% Here is an SQL select statement with both string types in the FROM clause
- SqlSelect =
- "select clientid, topic as tp "
- "from 't/tt', \"$events/client_connected\" ",
- ?assertMatch(
- {ok, #{<<"clientid">> := <<"abc">>, <<"tp">> := <<"t/tt">>}},
- emqx_rule_sqltester:test(
- #{
- sql => SqlSelect,
- context => #{clientid => <<"abc">>, topic => <<"t/tt">>}
- }
- )
- ),
- %% Here is an SQL foreach statement with both string types in the FROM clause
- SqlForeach =
- "foreach payload.sensors "
- "from 't/#', \"$events/client_connected\" ",
- ?assertMatch(
- {ok, []},
- emqx_rule_sqltester:test(
- #{
- sql => SqlForeach,
- context =>
- #{
- payload => <<"{\"sensors\": 1}">>,
- topic => <<"t/a">>
- }
- }
- )
- ).
- %%------------------------------------------------------------------------------
- %% Test cases for telemetry functions
- %%------------------------------------------------------------------------------
- t_get_basic_usage_info_0(_Config) ->
- ?assertEqual(
- #{
- num_rules => 0,
- referenced_bridges => #{}
- },
- emqx_rule_engine:get_basic_usage_info()
- ),
- ok.
- t_get_basic_usage_info_1(_Config) ->
- {ok, _} =
- emqx_rule_engine:create_rule(
- #{
- id => <<"rule:t_get_basic_usage_info:1">>,
- sql => <<"select 1 from topic">>,
- actions =>
- [
- #{function => <<"erlang:hibernate">>, args => #{}},
- #{function => console},
- <<"webhook:my_webhook">>,
- <<"webhook:my_webhook">>
- ]
- }
- ),
- {ok, _} =
- emqx_rule_engine:create_rule(
- #{
- id => <<"rule:t_get_basic_usage_info:2">>,
- sql => <<"select 1 from topic">>,
- actions =>
- [
- <<"mqtt:my_mqtt_bridge">>,
- <<"webhook:my_webhook">>
- ]
- }
- ),
- ?assertEqual(
- #{
- num_rules => 2,
- referenced_bridges =>
- #{
- mqtt => 1,
- webhook => 3
- }
- },
- emqx_rule_engine:get_basic_usage_info()
- ),
- ok.
- t_get_rule_ids_by_action_reference_ingress_bridge(_Config) ->
- BridgeId = <<"mqtt:ingress">>,
- RuleId = <<"rule:ingress_bridge_referenced">>,
- {ok, _} =
- emqx_rule_engine:create_rule(
- #{
- id => RuleId,
- sql => <<"select 1 from \"$bridges/", BridgeId/binary, "\"">>,
- actions => [#{function => console}]
- }
- ),
- on_exit(fun() -> emqx_rule_engine:delete_rule(RuleId) end),
- ?assertMatch(
- [RuleId],
- emqx_rule_engine:get_rule_ids_by_action(BridgeId)
- ),
- ok.
- %%------------------------------------------------------------------------------
- %% Test cases for rule metrics
- %%------------------------------------------------------------------------------
- -define(BRIDGE_TYPE, <<"mqtt">>).
- -define(BRIDGE_NAME, <<"bridge_over_troubled_water">>).
- -define(BRIDGE_CONFIG(QMODE), #{
- <<"server">> => <<"127.0.0.1:1883">>,
- <<"username">> => <<"user1">>,
- <<"password">> => <<"">>,
- <<"proto_ver">> => <<"v4">>,
- <<"ssl">> => #{<<"enable">> => false},
- <<"egress">> =>
- #{
- <<"local">> =>
- #{
- <<"topic">> => <<"foo/#">>
- },
- <<"remote">> =>
- #{
- <<"topic">> => <<"bar/${topic}">>,
- <<"payload">> => <<"${payload}">>,
- <<"qos">> => <<"${qos}">>,
- <<"retain">> => <<"${retain}">>
- }
- },
- <<"resource_opts">> =>
- #{
- <<"health_check_interval">> => <<"5s">>,
- <<"query_mode">> => QMODE,
- <<"request_ttl">> => <<"3s">>,
- <<"worker_pool_size">> => 1
- }
- }).
- -define(SUCCESSS_METRICS, #{
- matched := 1,
- 'actions.total' := 1,
- 'actions.failed' := 0,
- 'actions.success' := 1
- }).
- -define(FAIL_METRICS, #{
- matched := 1,
- 'actions.total' := 1,
- 'actions.failed' := 1,
- 'actions.success' := 0
- }).
- t_rule_metrics_sync(_Config) ->
- do_test_rule_metrics_success(<<"sync">>).
- t_rule_metrics_async(_Config) ->
- do_test_rule_metrics_success(<<"async">>).
- t_rule_metrics_sync_fail(_Config) ->
- do_test_rule_metrics_fail(<<"sync">>).
- t_rule_metrics_async_fail(_Config) ->
- do_test_rule_metrics_fail(<<"async">>).
- do_test_rule_metrics_success(QMode) ->
- ?assertMatch(
- ?SUCCESSS_METRICS,
- do_test_rule_metrics(QMode)
- ).
- do_test_rule_metrics_fail(QMode) ->
- ?assertMatch(
- ?FAIL_METRICS,
- do_test_rule_metrics(QMode)
- ).
- do_test_rule_metrics(QMode) ->
- BridgeId = create_bridge(?BRIDGE_TYPE, ?BRIDGE_NAME, ?BRIDGE_CONFIG(QMode)),
- RuleId = <<"rule:test_metrics_bridge_action">>,
- {ok, #{id := RuleId}} =
- emqx_rule_engine:create_rule(
- #{
- id => RuleId,
- sql => <<"SELECT * FROM \"topic/#\"">>,
- actions => [BridgeId]
- }
- ),
- timer:sleep(100),
- ?assertMatch(
- #{
- matched := 0,
- 'actions.total' := 0,
- 'actions.failed' := 0,
- 'actions.success' := 0
- },
- emqx_metrics_worker:get_counters(rule_metrics, RuleId)
- ),
- MsgId = emqx_guid:gen(),
- emqx:publish(#message{id = MsgId, topic = <<"topic/test">>, payload = <<"hello">>}),
- timer:sleep(100),
- on_exit(
- fun() ->
- emqx_rule_engine:delete_rule(RuleId),
- emqx_bridge:remove(?BRIDGE_TYPE, ?BRIDGE_NAME)
- end
- ),
- emqx_metrics_worker:get_counters(rule_metrics, RuleId).
- create_bridge(Type, Name, Config) ->
- {ok, _Bridge} = emqx_bridge:create(Type, Name, Config),
- emqx_bridge_resource:bridge_id(Type, Name).
- %%------------------------------------------------------------------------------
- %% Internal helpers
- %%------------------------------------------------------------------------------
- republish_action(Topic) ->
- republish_action(Topic, <<"${payload}">>).
- republish_action(Topic, Payload) ->
- republish_action(Topic, Payload, <<"${user_properties}">>).
- republish_action(Topic, Payload, UserProperties) ->
- #{
- function => republish,
- args => #{
- payload => Payload,
- topic => Topic,
- qos => 0,
- retain => false,
- user_properties => UserProperties
- }
- }.
- action_response(Selected, Envs, Args) ->
- ?tp(action_response, #{
- selected => Selected,
- envs => Envs,
- args => Args
- }),
- ok.
- make_simple_rule_with_ts(RuleId, Ts) when is_binary(RuleId) ->
- SQL = <<"select * from \"simple/topic\"">>,
- make_simple_rule(RuleId, SQL, Ts).
- make_simple_rule(RuleId) when is_binary(RuleId) ->
- SQL = <<"select * from \"simple/topic\"">>,
- make_simple_rule(RuleId, SQL).
- make_simple_rule(RuleId, SQL) when is_binary(RuleId) ->
- make_simple_rule(RuleId, SQL, erlang:system_time(millisecond)).
- make_simple_rule(RuleId, SQL, Ts) when is_binary(RuleId) ->
- #{
- id => RuleId,
- sql => SQL,
- actions => [#{function => console, args => #{}}],
- description => <<"simple rule">>,
- created_at => Ts
- }.
- action_record_triggered_events(Data = #{event := EventName}, _Envs, _Args) ->
- ct:pal("applying action_record_triggered_events: ~p", [Data]),
- ets:insert(events_record_tab, {EventName, Data}).
- verify_event(EventName) ->
- ct:sleep(50),
- case ets:lookup(events_record_tab, EventName) of
- [] ->
- ct:fail({no_such_event, EventName, ets:tab2list(events_record_tab)});
- Records ->
- [
- begin
- %% verify fields can be formatted to JSON string
- _ = emqx_utils_json:encode(Fields),
- %% verify metadata fields
- verify_metadata_fields(EventName, Fields),
- %% verify available fields for each event name
- verify_event_fields(EventName, Fields)
- end
- || {_Name, Fields} <- Records
- ]
- end.
- verify_metadata_fields(_EventName, #{metadata := Metadata}) ->
- ?assertMatch(
- #{rule_id := <<"rule:t_events">>},
- Metadata
- ).
- verify_event_fields('message.publish', Fields) ->
- #{
- id := ID,
- clientid := ClientId,
- username := Username,
- payload := Payload,
- peerhost := PeerHost,
- topic := Topic,
- qos := QoS,
- flags := Flags,
- pub_props := Properties,
- timestamp := Timestamp,
- publish_received_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(ID)),
- ?assertEqual(<<"c_event">>, ClientId),
- ?assertEqual(<<"u_event">>, Username),
- ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- ?assert(is_map(Flags)),
- ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('client.connected', Fields) ->
- #{
- clientid := ClientId,
- username := Username,
- mountpoint := MountPoint,
- peername := PeerName,
- sockname := SockName,
- proto_name := ProtoName,
- proto_ver := ProtoVer,
- keepalive := Keepalive,
- clean_start := CleanStart,
- expiry_interval := ExpiryInterval,
- is_bridge := IsBridge,
- conn_props := Properties,
- timestamp := Timestamp,
- connected_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(MountPoint) orelse MountPoint == undefined),
- ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
- ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
- verify_peername(PeerName),
- verify_peername(SockName),
- ?assertEqual(<<"MQTT">>, ProtoName),
- ?assertEqual(5, ProtoVer),
- ?assert(is_integer(Keepalive)),
- ?assert(is_boolean(CleanStart)),
- ?assertEqual(60, ExpiryInterval),
- ?assertEqual(false, IsBridge),
- ?assertMatch(#{'Session-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('client.disconnected', Fields) ->
- #{
- reason := Reason,
- clientid := ClientId,
- username := Username,
- peername := PeerName,
- sockname := SockName,
- disconn_props := Properties,
- timestamp := Timestamp,
- disconnected_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_atom(Reason)),
- ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
- ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
- verify_peername(PeerName),
- verify_peername(SockName),
- ?assertMatch(#{'User-Property' := #{<<"reason">> := <<"normal">>}}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields(SubUnsub, Fields) when
- SubUnsub == 'session.subscribed';
- SubUnsub == 'session.unsubscribed'
- ->
- #{
- clientid := ClientId,
- username := Username,
- peerhost := PeerHost,
- topic := Topic,
- qos := QoS,
- timestamp := Timestamp
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- ?assert(is_atom(reason)),
- ?assertEqual(<<"c_event2">>, ClientId),
- ?assertEqual(<<"u_event2">>, Username),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- PropKey =
- case SubUnsub of
- 'session.subscribed' -> sub_props;
- 'session.unsubscribed' -> unsub_props
- end,
- ?assertMatch(
- #{'User-Property' := #{<<"topic_name">> := <<"t1">>}},
- maps:get(PropKey, Fields)
- ),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000);
- verify_event_fields('delivery.dropped', Fields) ->
- #{
- event := 'delivery.dropped',
- id := ID,
- metadata := #{rule_id := RuleId},
- reason := Reason,
- clientid := ClientId,
- username := Username,
- from_clientid := FromClientId,
- from_username := FromUsername,
- node := Node,
- payload := Payload,
- peerhost := PeerHost,
- pub_props := Properties,
- publish_received_at := EventAt,
- qos := QoS,
- flags := Flags,
- timestamp := Timestamp,
- topic := Topic
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(ID)),
- ?assertEqual(<<"rule:t_events">>, RuleId),
- ?assertEqual(no_local, Reason),
- ?assertEqual(node(), Node),
- ?assertEqual(<<"c_event">>, ClientId),
- ?assertEqual(<<"u_event">>, Username),
- ?assertEqual(<<"c_event">>, FromClientId),
- ?assertEqual(<<"u_event">>, FromUsername),
- ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- ?assert(is_map(Flags)),
- ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('message.dropped', Fields) ->
- #{
- id := ID,
- reason := Reason,
- clientid := ClientId,
- username := Username,
- payload := Payload,
- peerhost := PeerHost,
- topic := Topic,
- qos := QoS,
- flags := Flags,
- pub_props := Properties,
- timestamp := Timestamp,
- publish_received_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(ID)),
- ?assert(is_atom(Reason)),
- ?assertEqual(<<"c_event">>, ClientId),
- ?assertEqual(<<"u_event">>, Username),
- ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- ?assert(is_map(Flags)),
- ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('message.delivered', Fields) ->
- #{
- id := ID,
- clientid := ClientId,
- username := Username,
- from_clientid := FromClientId,
- from_username := FromUsername,
- payload := Payload,
- peerhost := PeerHost,
- topic := Topic,
- qos := QoS,
- flags := Flags,
- pub_props := Properties,
- timestamp := Timestamp,
- publish_received_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(ID)),
- ?assertEqual(<<"c_event2">>, ClientId),
- ?assertEqual(<<"u_event2">>, Username),
- ?assertEqual(<<"c_event">>, FromClientId),
- ?assertEqual(<<"u_event">>, FromUsername),
- ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- ?assert(is_map(Flags)),
- ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('message.acked', Fields) ->
- #{
- id := ID,
- clientid := ClientId,
- username := Username,
- from_clientid := FromClientId,
- from_username := FromUsername,
- payload := Payload,
- peerhost := PeerHost,
- topic := Topic,
- qos := QoS,
- flags := Flags,
- pub_props := PubProps,
- puback_props := PubAckProps,
- timestamp := Timestamp,
- publish_received_at := EventAt
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- RcvdAtElapse = Now - EventAt,
- ?assert(is_binary(ID)),
- ?assertEqual(<<"c_event2">>, ClientId),
- ?assertEqual(<<"u_event2">>, Username),
- ?assertEqual(<<"c_event">>, FromClientId),
- ?assertEqual(<<"u_event">>, FromUsername),
- ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
- verify_ipaddr(PeerHost),
- ?assertEqual(<<"t1">>, Topic),
- ?assertEqual(1, QoS),
- ?assert(is_map(Flags)),
- ?assertMatch(#{'Message-Expiry-Interval' := 60}, PubProps),
- ?assert(is_map(PubAckProps)),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
- ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
- ?assert(EventAt =< Timestamp);
- verify_event_fields('client.connack', Fields) ->
- #{
- clientid := ClientId,
- clean_start := CleanStart,
- username := Username,
- peername := PeerName,
- sockname := SockName,
- proto_name := ProtoName,
- proto_ver := ProtoVer,
- keepalive := Keepalive,
- expiry_interval := ExpiryInterval,
- conn_props := Properties,
- reason_code := Reason,
- timestamp := Timestamp
- } = Fields,
- Now = erlang:system_time(millisecond),
- TimestampElapse = Now - Timestamp,
- ?assert(lists:member(Reason, [success, bad_username_or_password])),
- ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>, <<"c_event3">>])),
- ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>, <<"u_event3">>])),
- verify_peername(PeerName),
- verify_peername(SockName),
- ?assertEqual(<<"MQTT">>, ProtoName),
- ?assertEqual(5, ProtoVer),
- ?assert(is_integer(Keepalive)),
- ?assert(is_boolean(CleanStart)),
- ?assertEqual(60000, ExpiryInterval),
- ?assertMatch(#{'Session-Expiry-Interval' := 60}, Properties),
- ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000);
- verify_event_fields('client.check_authz_complete', Fields) ->
- #{
- clientid := ClientId,
- action := Action,
- result := Result,
- topic := Topic,
- authz_source := AuthzSource,
- username := Username
- } = Fields,
- ?assertEqual(<<"t1">>, Topic),
- ?assert(lists:member(Action, [subscribe, publish])),
- ?assert(lists:member(Result, [allow, deny])),
- ?assert(
- lists:member(AuthzSource, [
- cache,
- default,
- file,
- http,
- mongodb,
- mysql,
- redis,
- postgresql,
- built_in_database
- ])
- ),
- ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
- ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])).
- verify_peername(PeerName) ->
- case string:split(PeerName, ":") of
- [IPAddrS, PortS] ->
- verify_ipaddr(IPAddrS),
- _ = binary_to_integer(PortS);
- _ ->
- ct:fail({invalid_peername, PeerName})
- end.
- verify_ipaddr(IPAddrS) ->
- ?assertMatch({ok, _}, inet:parse_address(binary_to_list(IPAddrS))).
- init_events_counters() ->
- ets:new(events_record_tab, [named_table, bag, public]).
- user_properties(PairsMap) ->
- #{'User-Property' => maps:to_list(PairsMap)}.
- %%------------------------------------------------------------------------------
- %% Start Apps
- %%------------------------------------------------------------------------------
- deps_path(App, RelativePath) ->
- Path0 = code:lib_dir(App),
- Path =
- case file:read_link(Path0) of
- {ok, Resolved} -> Resolved;
- {error, _} -> Path0
- end,
- filename:join([Path, RelativePath]).
- local_path(RelativePath) ->
- deps_path(emqx_rule_engine, RelativePath).
- create_rules(Rules) ->
- lists:foreach(fun create_rule/1, Rules).
- create_rule(Rule) ->
- {ok, _} = emqx_rule_engine:create_rule(Rule),
- ok.
- delete_rules_by_ids(Ids) ->
- lists:foreach(
- fun(Id) ->
- ok = emqx_rule_engine:delete_rule(Id)
- end,
- Ids
- ).
- delete_rule(#{id := Id}) ->
- ok = emqx_rule_engine:delete_rule(Id);
- delete_rule(Id) when is_binary(Id) ->
- ok = emqx_rule_engine:delete_rule(Id).
|