|
|
@@ -63,7 +63,9 @@ single_config_tests() ->
|
|
|
t_get_status_down,
|
|
|
t_get_status_no_worker,
|
|
|
t_get_status_timeout_calling_workers,
|
|
|
- t_on_start_ehttpc_pool_already_started
|
|
|
+ t_on_start_ehttpc_pool_already_started,
|
|
|
+ t_attributes,
|
|
|
+ t_bad_attributes
|
|
|
].
|
|
|
|
|
|
only_sync_tests() ->
|
|
|
@@ -212,7 +214,9 @@ create_bridge_http(Config, GCPPubSubConfigOverrides) ->
|
|
|
Error
|
|
|
end,
|
|
|
ct:pal("bridge creation result: ~p", [Res]),
|
|
|
- ?assertEqual(element(1, ProbeResult), element(1, Res)),
|
|
|
+ ?assertEqual(element(1, ProbeResult), element(1, Res), #{
|
|
|
+ creation_result => Res, probe_result => ProbeResult
|
|
|
+ }),
|
|
|
case ProbeResult of
|
|
|
{error, {{_, 500, _}, _, _}} -> error({bad_probe_result, ProbeResult});
|
|
|
_ -> ok
|
|
|
@@ -456,6 +460,7 @@ assert_valid_request_headers(Headers, ServiceAccountJSON) ->
|
|
|
assert_valid_request_body(Body) ->
|
|
|
BodyMap = emqx_utils_json:decode(Body, [return_maps]),
|
|
|
?assertMatch(#{<<"messages">> := [_ | _]}, BodyMap),
|
|
|
+ ct:pal("request: ~p", [BodyMap]),
|
|
|
#{<<"messages">> := Messages} = BodyMap,
|
|
|
lists:map(
|
|
|
fun(Msg) ->
|
|
|
@@ -480,6 +485,31 @@ assert_http_request(ServiceAccountJSON) ->
|
|
|
error({timeout, #{mailbox => Mailbox}})
|
|
|
end.
|
|
|
|
|
|
+receive_http_requests(ServiceAccountJSON, Opts) ->
|
|
|
+ Default = #{n => 1},
|
|
|
+ #{n := N} = maps:merge(Default, Opts),
|
|
|
+ lists:flatmap(fun(_) -> receive_http_request(ServiceAccountJSON) end, lists:seq(1, N)).
|
|
|
+
|
|
|
+receive_http_request(ServiceAccountJSON) ->
|
|
|
+ receive
|
|
|
+ {http, Headers, Body} ->
|
|
|
+ ct:pal("received publish:\n ~p", [#{headers => Headers, body => Body}]),
|
|
|
+ assert_valid_request_headers(Headers, ServiceAccountJSON),
|
|
|
+ #{<<"messages">> := Msgs} = emqx_utils_json:decode(Body, [return_maps]),
|
|
|
+ lists:map(
|
|
|
+ fun(Msg) ->
|
|
|
+ #{<<"data">> := Content64} = Msg,
|
|
|
+ Content = base64:decode(Content64),
|
|
|
+ Decoded = emqx_utils_json:decode(Content, [return_maps]),
|
|
|
+ Msg#{<<"data">> := Decoded}
|
|
|
+ end,
|
|
|
+ Msgs
|
|
|
+ )
|
|
|
+ after 5_000 ->
|
|
|
+ {messages, Mailbox} = process_info(self(), messages),
|
|
|
+ error({timeout, #{mailbox => Mailbox}})
|
|
|
+ end.
|
|
|
+
|
|
|
install_telemetry_handler(TestCase) ->
|
|
|
Tid = ets:new(TestCase, [ordered_set, public]),
|
|
|
HandlerId = TestCase,
|
|
|
@@ -585,8 +615,8 @@ t_publish_success(Config) ->
|
|
|
<<"topic">> := Topic,
|
|
|
<<"payload">> := Payload,
|
|
|
<<"metadata">> := #{<<"rule_id">> := RuleId}
|
|
|
- }
|
|
|
- ],
|
|
|
+ } = Msg
|
|
|
+ ] when not (is_map_key(<<"attributes">>, Msg) orelse is_map_key(<<"orderingKey">>, Msg)),
|
|
|
DecodedMessages
|
|
|
),
|
|
|
%% to avoid test flakiness
|
|
|
@@ -1524,3 +1554,251 @@ t_query_sync(Config) ->
|
|
|
[]
|
|
|
),
|
|
|
ok.
|
|
|
+
|
|
|
+t_attributes(Config) ->
|
|
|
+ Name = ?config(gcp_pubsub_name, Config),
|
|
|
+ ServiceAccountJSON = ?config(service_account_json, Config),
|
|
|
+ LocalTopic = <<"t/topic">>,
|
|
|
+ ?check_trace(
|
|
|
+ begin
|
|
|
+ {ok, _} = create_bridge_http(
|
|
|
+ Config,
|
|
|
+ #{
|
|
|
+ <<"local_topic">> => LocalTopic,
|
|
|
+ <<"attributes_template">> =>
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"${.payload.key}">>,
|
|
|
+ <<"value">> => <<"fixed_value">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"${.payload.key}2">>,
|
|
|
+ <<"value">> => <<"${.payload.value}">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"fixed_key">>,
|
|
|
+ <<"value">> => <<"fixed_value">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"fixed_key2">>,
|
|
|
+ <<"value">> => <<"${.payload.value}">>
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ <<"ordering_key_template">> => <<"${.payload.ok}">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ %% without ordering key
|
|
|
+ Payload0 =
|
|
|
+ emqx_utils_json:encode(
|
|
|
+ #{
|
|
|
+ <<"value">> => <<"payload_value">>,
|
|
|
+ <<"key">> => <<"payload_key">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ Message0 = emqx_message:make(LocalTopic, Payload0),
|
|
|
+ emqx:publish(Message0),
|
|
|
+ DecodedMessages0 = receive_http_request(ServiceAccountJSON),
|
|
|
+ ?assertMatch(
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"attributes">> :=
|
|
|
+ #{
|
|
|
+ <<"fixed_key">> := <<"fixed_value">>,
|
|
|
+ <<"fixed_key2">> := <<"payload_value">>,
|
|
|
+ <<"payload_key">> := <<"fixed_value">>,
|
|
|
+ <<"payload_key2">> := <<"payload_value">>
|
|
|
+ },
|
|
|
+ <<"data">> := #{
|
|
|
+ <<"topic">> := _,
|
|
|
+ <<"payload">> := _
|
|
|
+ }
|
|
|
+ } = Msg
|
|
|
+ ] when not is_map_key(<<"orderingKey">>, Msg),
|
|
|
+ DecodedMessages0
|
|
|
+ ),
|
|
|
+ %% with ordering key
|
|
|
+ Payload1 =
|
|
|
+ emqx_utils_json:encode(
|
|
|
+ #{
|
|
|
+ <<"value">> => <<"payload_value">>,
|
|
|
+ <<"key">> => <<"payload_key">>,
|
|
|
+ <<"ok">> => <<"ordering_key">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ Message1 = emqx_message:make(LocalTopic, Payload1),
|
|
|
+ emqx:publish(Message1),
|
|
|
+ DecodedMessages1 = receive_http_request(ServiceAccountJSON),
|
|
|
+ ?assertMatch(
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"attributes">> :=
|
|
|
+ #{
|
|
|
+ <<"fixed_key">> := <<"fixed_value">>,
|
|
|
+ <<"fixed_key2">> := <<"payload_value">>,
|
|
|
+ <<"payload_key">> := <<"fixed_value">>,
|
|
|
+ <<"payload_key2">> := <<"payload_value">>
|
|
|
+ },
|
|
|
+ <<"orderingKey">> := <<"ordering_key">>,
|
|
|
+ <<"data">> := #{
|
|
|
+ <<"topic">> := _,
|
|
|
+ <<"payload">> := _
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ DecodedMessages1
|
|
|
+ ),
|
|
|
+ %% will result in empty key
|
|
|
+ Payload2 =
|
|
|
+ emqx_utils_json:encode(
|
|
|
+ #{
|
|
|
+ <<"value">> => <<"payload_value">>,
|
|
|
+ <<"ok">> => <<"ordering_key">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ Message2 = emqx_message:make(LocalTopic, Payload2),
|
|
|
+ emqx:publish(Message2),
|
|
|
+ [DecodedMessage2] = receive_http_request(ServiceAccountJSON),
|
|
|
+ ?assertEqual(
|
|
|
+ #{
|
|
|
+ <<"fixed_key">> => <<"fixed_value">>,
|
|
|
+ <<"fixed_key2">> => <<"payload_value">>,
|
|
|
+ <<"2">> => <<"payload_value">>
|
|
|
+ },
|
|
|
+ maps:get(<<"attributes">>, DecodedMessage2)
|
|
|
+ ),
|
|
|
+ %% ensure loading cluster override file doesn't mangle the attribute
|
|
|
+ %% placeholders...
|
|
|
+ #{<<"bridges">> := #{?BRIDGE_TYPE_BIN := #{Name := RawConf}}} =
|
|
|
+ emqx_config:read_override_conf(#{override_to => cluster}),
|
|
|
+ ?assertEqual(
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"${.payload.key}">>,
|
|
|
+ <<"value">> => <<"fixed_value">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"${.payload.key}2">>,
|
|
|
+ <<"value">> => <<"${.payload.value}">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"fixed_key">>,
|
|
|
+ <<"value">> => <<"fixed_value">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"fixed_key2">>,
|
|
|
+ <<"value">> => <<"${.payload.value}">>
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ maps:get(<<"attributes_template">>, RawConf)
|
|
|
+ ),
|
|
|
+ ok
|
|
|
+ end,
|
|
|
+ []
|
|
|
+ ),
|
|
|
+ ok.
|
|
|
+
|
|
|
+t_bad_attributes(Config) ->
|
|
|
+ ServiceAccountJSON = ?config(service_account_json, Config),
|
|
|
+ LocalTopic = <<"t/topic">>,
|
|
|
+ ?check_trace(
|
|
|
+ begin
|
|
|
+ {ok, _} = create_bridge_http(
|
|
|
+ Config,
|
|
|
+ #{
|
|
|
+ <<"local_topic">> => LocalTopic,
|
|
|
+ <<"attributes_template">> =>
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"key">> => <<"${.payload.key}">>,
|
|
|
+ <<"value">> => <<"${.payload.value}">>
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ <<"ordering_key_template">> => <<"${.payload.ok}">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ %% Ok: attribute value is a map or list
|
|
|
+ lists:foreach(
|
|
|
+ fun(OkValue) ->
|
|
|
+ Payload0 =
|
|
|
+ emqx_utils_json:encode(
|
|
|
+ #{
|
|
|
+ <<"ok">> => <<"ord_key">>,
|
|
|
+ <<"value">> => OkValue,
|
|
|
+ <<"key">> => <<"attr_key">>
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ Message0 = emqx_message:make(LocalTopic, Payload0),
|
|
|
+ emqx:publish(Message0)
|
|
|
+ end,
|
|
|
+ [
|
|
|
+ #{<<"some">> => <<"map">>},
|
|
|
+ [1, <<"str">>, #{<<"deep">> => true}]
|
|
|
+ ]
|
|
|
+ ),
|
|
|
+ DecodedMessages0 = receive_http_requests(ServiceAccountJSON, #{n => 1}),
|
|
|
+ ?assertMatch(
|
|
|
+ [
|
|
|
+ #{
|
|
|
+ <<"attributes">> :=
|
|
|
+ #{<<"attr_key">> := <<"{\"some\":\"map\"}">>},
|
|
|
+ <<"orderingKey">> := <<"ord_key">>
|
|
|
+ },
|
|
|
+ #{
|
|
|
+ <<"attributes">> :=
|
|
|
+ #{<<"attr_key">> := <<"[1,\"str\",{\"deep\":true}]">>},
|
|
|
+ <<"orderingKey">> := <<"ord_key">>
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ DecodedMessages0
|
|
|
+ ),
|
|
|
+ %% Bad: key is not a plain value
|
|
|
+ lists:foreach(
|
|
|
+ fun(BadKey) ->
|
|
|
+ Payload1 =
|
|
|
+ emqx_utils_json:encode(
|
|
|
+ #{
|
|
|
+ <<"value">> => <<"v">>,
|
|
|
+ <<"key">> => BadKey,
|
|
|
+ <<"ok">> => BadKey
|
|
|
+ }
|
|
|
+ ),
|
|
|
+ Message1 = emqx_message:make(LocalTopic, Payload1),
|
|
|
+ emqx:publish(Message1)
|
|
|
+ end,
|
|
|
+ [
|
|
|
+ #{<<"some">> => <<"map">>},
|
|
|
+ [1, <<"list">>, true],
|
|
|
+ true,
|
|
|
+ false
|
|
|
+ ]
|
|
|
+ ),
|
|
|
+ DecodedMessages1 = receive_http_request(ServiceAccountJSON),
|
|
|
+ lists:foreach(
|
|
|
+ fun(DMsg) ->
|
|
|
+ ?assertNot(is_map_key(<<"orderingKey">>, DMsg), #{decoded_message => DMsg}),
|
|
|
+ ?assertNot(is_map_key(<<"attributes">>, DMsg), #{decoded_message => DMsg}),
|
|
|
+ ok
|
|
|
+ end,
|
|
|
+ DecodedMessages1
|
|
|
+ ),
|
|
|
+ ok
|
|
|
+ end,
|
|
|
+ fun(Trace) ->
|
|
|
+ ct:pal("trace:\n ~p", [Trace]),
|
|
|
+ ?assertMatch(
|
|
|
+ [
|
|
|
+ #{placeholder := [<<"payload">>, <<"ok">>], value := #{}},
|
|
|
+ #{placeholder := [<<"payload">>, <<"key">>], value := #{}},
|
|
|
+ #{placeholder := [<<"payload">>, <<"ok">>], value := [_ | _]},
|
|
|
+ #{placeholder := [<<"payload">>, <<"key">>], value := [_ | _]},
|
|
|
+ #{placeholder := [<<"payload">>, <<"ok">>], value := true},
|
|
|
+ #{placeholder := [<<"payload">>, <<"key">>], value := true},
|
|
|
+ #{placeholder := [<<"payload">>, <<"ok">>], value := false},
|
|
|
+ #{placeholder := [<<"payload">>, <<"key">>], value := false}
|
|
|
+ ],
|
|
|
+ ?of_kind("gcp_pubsub_producer_bad_value_for_key", Trace)
|
|
|
+ ),
|
|
|
+ ok
|
|
|
+ end
|
|
|
+ ),
|
|
|
+ ok.
|