Move selenium to the root of the repo
This commit is contained in:
parent
77b63d6799
commit
a1206dc801
|
@ -42,7 +42,7 @@ jobs:
|
||||||
- erlang_version: "26.2"
|
- erlang_version: "26.2"
|
||||||
elixir_version: 1.15.7
|
elixir_version: 1.15.7
|
||||||
env:
|
env:
|
||||||
SELENIUM_DIR: deps/rabbitmq_management/selenium
|
SELENIUM_DIR: selenium
|
||||||
DOCKER_NETWORK: rabbitmq_net
|
DOCKER_NETWORK: rabbitmq_net
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -91,7 +91,8 @@ jobs:
|
||||||
|
|
||||||
- name: Run Suites
|
- name: Run Suites
|
||||||
run: |
|
run: |
|
||||||
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging
|
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \
|
||||||
|
${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging
|
||||||
|
|
||||||
- name: Upload Test Artifacts
|
- name: Upload Test Artifacts
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
name: Test Management UI with Selenium for PRs
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'deps/**'
|
||||||
|
- 'selenium/**'
|
||||||
|
- .github/workflows/test-management-ui-for-pr.yaml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
selenium:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
erlang_version:
|
||||||
|
- "26.2"
|
||||||
|
browser:
|
||||||
|
- chrome
|
||||||
|
include:
|
||||||
|
- erlang_version: "26.2"
|
||||||
|
elixir_version: 1.15.7
|
||||||
|
env:
|
||||||
|
SELENIUM_DIR: selenium
|
||||||
|
DOCKER_NETWORK: rabbitmq_net
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure OTP & Elixir
|
||||||
|
uses: erlef/setup-beam@v1.17
|
||||||
|
with:
|
||||||
|
otp-version: ${{ matrix.erlang_version }}
|
||||||
|
elixir-version: ${{ matrix.elixir_version }}
|
||||||
|
hexpm-mirrors: |
|
||||||
|
https://builds.hex.pm
|
||||||
|
https://cdn.jsdelivr.net/hex
|
||||||
|
|
||||||
|
- name: Authenticate To Google Cloud
|
||||||
|
uses: google-github-actions/auth@v2.1.5
|
||||||
|
with:
|
||||||
|
credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }}
|
||||||
|
|
||||||
|
- name: Configure Bazel
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then
|
||||||
|
cat << EOF >> user.bazelrc
|
||||||
|
build --remote_cache=https://storage.googleapis.com/${{ secrets.REMOTE_CACHE_BUCKET_NAME }}
|
||||||
|
build --google_default_credentials
|
||||||
|
|
||||||
|
build --remote_download_toplevel
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
cat << EOF >> user.bazelrc
|
||||||
|
build --color=yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build & Load RabbitMQ OCI
|
||||||
|
run: |
|
||||||
|
bazelisk run packaging/docker-image:rabbitmq-amd64
|
||||||
|
|
||||||
|
- name: Configure Docker Network
|
||||||
|
run: |
|
||||||
|
docker network create ${DOCKER_NETWORK}
|
||||||
|
|
||||||
|
- name: Build Test Runner Image
|
||||||
|
run: |
|
||||||
|
cd ${SELENIUM_DIR}
|
||||||
|
docker build -t mocha-test --target test .
|
||||||
|
|
||||||
|
- name: Run full ui suites on a standalone rabbitmq server
|
||||||
|
run: |
|
||||||
|
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \
|
||||||
|
${SELENIUM_DIR}/run-suites.sh
|
||||||
|
mkdir -p /tmp/full-suite
|
||||||
|
mv /tmp/selenium/* /tmp/full-suite
|
||||||
|
mkdir -p /tmp/full-suite/logs
|
||||||
|
mv ${SELENIUM_DIR}/logs/* /tmp/full-suite/logs
|
||||||
|
mkdir -p /tmp/full-suite/screens
|
||||||
|
mv ${SELENIUM_DIR}/screens/* /tmp/full-suite/screens
|
||||||
|
|
||||||
|
- name: Upload Test Artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4.3.2
|
||||||
|
with:
|
||||||
|
name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }}
|
||||||
|
path: |
|
||||||
|
/tmp/full-suite
|
||||||
|
/tmp/short-suite
|
||||||
|
|
||||||
|
summary-selenium:
|
||||||
|
needs:
|
||||||
|
- selenium
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: SUMMARY
|
||||||
|
run: |
|
||||||
|
echo "SUCCESS"
|
|
@ -16,11 +16,9 @@ on:
|
||||||
- BUILD.*
|
- BUILD.*
|
||||||
- '*.bzl'
|
- '*.bzl'
|
||||||
- '*.bazel'
|
- '*.bazel'
|
||||||
- .github/workflows/test-selenium.yaml
|
- 'selenium/**'
|
||||||
pull_request:
|
- .github/workflows/test-management-ui.yaml
|
||||||
paths:
|
|
||||||
- 'deps/rabbitmq_management/**'
|
|
||||||
- .github/workflows/test-selenium-for-pull-requests.yaml
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
@ -38,7 +36,7 @@ jobs:
|
||||||
- erlang_version: "26.2"
|
- erlang_version: "26.2"
|
||||||
elixir_version: 1.15.7
|
elixir_version: 1.15.7
|
||||||
env:
|
env:
|
||||||
SELENIUM_DIR: deps/rabbitmq_management/selenium
|
SELENIUM_DIR: selenium
|
||||||
DOCKER_NETWORK: rabbitmq_net
|
DOCKER_NETWORK: rabbitmq_net
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -85,17 +83,6 @@ jobs:
|
||||||
cd ${SELENIUM_DIR}
|
cd ${SELENIUM_DIR}
|
||||||
docker build -t mocha-test --target test .
|
docker build -t mocha-test --target test .
|
||||||
|
|
||||||
- name: Run full ui suites on a standalone rabbitmq server
|
|
||||||
run: |
|
|
||||||
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \
|
|
||||||
${SELENIUM_DIR}/run-suites.sh
|
|
||||||
mkdir -p /tmp/full-suite
|
|
||||||
mv /tmp/selenium/* /tmp/full-suite
|
|
||||||
mkdir -p /tmp/full-suite/logs
|
|
||||||
mv ${SELENIUM_DIR}/logs/* /tmp/full-suite/logs
|
|
||||||
mkdir -p /tmp/full-suite/screens
|
|
||||||
mv ${SELENIUM_DIR}/screens/* /tmp/full-suite/screens
|
|
||||||
|
|
||||||
- name: Run short ui suite on a 3-node rabbitmq cluster
|
- name: Run short ui suite on a 3-node rabbitmq cluster
|
||||||
run: |
|
run: |
|
||||||
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \
|
RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \
|
|
@ -1,5 +1,5 @@
|
||||||
[
|
[
|
||||||
{oauth2_pem_config2,
|
{root_resource_server,
|
||||||
"auth_oauth2.resource_server_id = new_resource_server_id
|
"auth_oauth2.resource_server_id = new_resource_server_id
|
||||||
auth_oauth2.scope_prefix = new_resource_server_id.
|
auth_oauth2.scope_prefix = new_resource_server_id.
|
||||||
auth_oauth2.resource_server_type = new_resource_server_type
|
auth_oauth2.resource_server_type = new_resource_server_type
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
]}
|
]}
|
||||||
],[]
|
],[]
|
||||||
},
|
},
|
||||||
{oauth2_pem_config3,
|
{multiple_resource_servers,
|
||||||
"auth_oauth2.resource_server_id = new_resource_server_id
|
"auth_oauth2.resource_server_id = new_resource_server_id
|
||||||
auth_oauth2.scope_prefix = new_resource_server_id.
|
auth_oauth2.scope_prefix = new_resource_server_id.
|
||||||
auth_oauth2.resource_server_type = new_resource_server_type
|
auth_oauth2.resource_server_type = new_resource_server_type
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
],
|
],
|
||||||
<<"rabbitmq-customers">> => [
|
<<"rabbitmq-customers">> => [
|
||||||
{additional_scopes_key, <<"roles">>},
|
{additional_scopes_key, <<"roles">>},
|
||||||
{id, <<"rabbitmq-customers">>}
|
{id, <<"rabbitmq-customers">>}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
]}
|
]}
|
||||||
],[]
|
],[]
|
||||||
},
|
},
|
||||||
{oauth2_pem_config4,
|
{multiple_oauth_providers,
|
||||||
"auth_oauth2.resource_server_id = new_resource_server_id
|
"auth_oauth2.resource_server_id = new_resource_server_id
|
||||||
auth_oauth2.scope_prefix = new_resource_server_id.
|
auth_oauth2.scope_prefix = new_resource_server_id.
|
||||||
auth_oauth2.resource_server_type = new_resource_server_type
|
auth_oauth2.resource_server_type = new_resource_server_type
|
||||||
|
@ -174,5 +174,15 @@
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
],[]
|
],[]
|
||||||
|
},
|
||||||
|
{empty_scope_prefix,
|
||||||
|
"auth_oauth2.resource_server_id = new_resource_server_id
|
||||||
|
auth_oauth2.scope_prefix = '' ",
|
||||||
|
[
|
||||||
|
{rabbitmq_auth_backend_oauth2, [
|
||||||
|
{resource_server_id,<<"new_resource_server_id">>},
|
||||||
|
{scope_prefix,<<>>}
|
||||||
|
]}
|
||||||
|
],[]
|
||||||
}
|
}
|
||||||
].
|
].
|
||||||
|
|
|
@ -130,6 +130,14 @@ groups() -> [
|
||||||
is_verify_aud_for_resource_one_returns_true,
|
is_verify_aud_for_resource_one_returns_true,
|
||||||
is_verify_aud_for_resource_two_returns_false
|
is_verify_aud_for_resource_two_returns_false
|
||||||
]},
|
]},
|
||||||
|
get_scope_prefix_for_resource_one_returns_default_scope_prefix,
|
||||||
|
{with_root_scope_prefix, [], [
|
||||||
|
get_scope_prefix_for_resource_one_returns_root_scope_prefix,
|
||||||
|
{with_empty_scope_prefix_for_resource_one, [], [
|
||||||
|
get_scope_prefix_for_resource_one_returns_empty_scope_prefix,
|
||||||
|
get_scope_prefix_for_resource_two_returns_root_scope_prefix
|
||||||
|
]}
|
||||||
|
]},
|
||||||
{with_jwks_url, [], [
|
{with_jwks_url, [], [
|
||||||
get_oauth_provider_for_both_resources_should_return_root_oauth_provider,
|
get_oauth_provider_for_both_resources_should_return_root_oauth_provider,
|
||||||
{with_oauth_providers_A_with_jwks_uri, [], [
|
{with_oauth_providers_A_with_jwks_uri, [], [
|
||||||
|
@ -160,6 +168,7 @@ groups() -> [
|
||||||
get_default_preferred_username_claims,
|
get_default_preferred_username_claims,
|
||||||
get_preferred_username_claims,
|
get_preferred_username_claims,
|
||||||
get_scope_prefix,
|
get_scope_prefix,
|
||||||
|
get_empty_scope_prefix,
|
||||||
get_scope_prefix_when_not_defined,
|
get_scope_prefix_when_not_defined,
|
||||||
get_resource_server_type,
|
get_resource_server_type,
|
||||||
get_resource_server_type_when_not_defined,
|
get_resource_server_type_when_not_defined,
|
||||||
|
@ -309,6 +318,16 @@ init_per_group(with_resource_server_id, Config) ->
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ),
|
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ),
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
|
init_per_group(with_root_scope_prefix, Config) ->
|
||||||
|
application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"some-prefix:">>),
|
||||||
|
Config;
|
||||||
|
init_per_group(with_empty_scope_prefix_for_resource_one, Config) ->
|
||||||
|
ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}),
|
||||||
|
Proplist = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers, []),
|
||||||
|
application:set_env(rabbitmq_auth_backend_oauth2, resource_servers,
|
||||||
|
maps:put(?RABBITMQ_RESOURCE_ONE, [{scope_prefix, <<"">>} | proplists:delete(scope_prefix, Proplist)], ResourceServers)),
|
||||||
|
Config;
|
||||||
|
|
||||||
init_per_group(with_verify_aud_false, Config) ->
|
init_per_group(with_verify_aud_false, Config) ->
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false),
|
application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false),
|
||||||
Config;
|
Config;
|
||||||
|
@ -405,7 +424,9 @@ end_per_group(with_root_static_signing_keys, Config) ->
|
||||||
KeyConfig = call_get_env(Config, key_config, []),
|
KeyConfig = call_get_env(Config, key_config, []),
|
||||||
call_set_env(Config, key_config, KeyConfig),
|
call_set_env(Config, key_config, KeyConfig),
|
||||||
Config;
|
Config;
|
||||||
|
end_per_group(get_empty_scope_prefix, Config) ->
|
||||||
|
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
||||||
|
Config;
|
||||||
end_per_group(with_resource_server_id, Config) ->
|
end_per_group(with_resource_server_id, Config) ->
|
||||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id),
|
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id),
|
||||||
Config;
|
Config;
|
||||||
|
@ -418,6 +439,13 @@ end_per_group(with_verify_aud_false_for_resource_two, Config) ->
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_servers,
|
application:set_env(rabbitmq_auth_backend_oauth2, resource_servers,
|
||||||
maps:put(?RABBITMQ_RESOURCE_TWO, proplists:delete(verify_aud, Proplist), ResourceServers)),
|
maps:put(?RABBITMQ_RESOURCE_TWO, proplists:delete(verify_aud, Proplist), ResourceServers)),
|
||||||
Config;
|
Config;
|
||||||
|
end_per_group(with_empty_scope_prefix_for_resource_one, Config) ->
|
||||||
|
ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}),
|
||||||
|
Proplist = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers, []),
|
||||||
|
application:set_env(rabbitmq_auth_backend_oauth2, resource_servers,
|
||||||
|
maps:put(?RABBITMQ_RESOURCE_ONE, proplists:delete(scope_prefix, Proplist), ResourceServers)),
|
||||||
|
Config;
|
||||||
|
|
||||||
end_per_group(with_default_key, Config) ->
|
end_per_group(with_default_key, Config) ->
|
||||||
KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
|
KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config,
|
application:set_env(rabbitmq_auth_backend_oauth2, key_config,
|
||||||
|
@ -507,6 +535,10 @@ end_per_group(inheritance_group, Config) ->
|
||||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers),
|
application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers),
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
|
end_per_group(with_root_scope_prefix, Config) ->
|
||||||
|
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
||||||
|
Config;
|
||||||
|
|
||||||
end_per_group(_any, Config) ->
|
end_per_group(_any, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
@ -520,6 +552,9 @@ init_per_testcase(get_additional_scopes_key_when_not_defined, Config) ->
|
||||||
init_per_testcase(is_verify_aud_when_is_false, Config) ->
|
init_per_testcase(is_verify_aud_when_is_false, Config) ->
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false),
|
application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false),
|
||||||
Config;
|
Config;
|
||||||
|
init_per_testcase(get_empty_scope_prefix, Config) ->
|
||||||
|
application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"">>),
|
||||||
|
Config;
|
||||||
init_per_testcase(get_scope_prefix_when_not_defined, Config) ->
|
init_per_testcase(get_scope_prefix_when_not_defined, Config) ->
|
||||||
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
||||||
Config;
|
Config;
|
||||||
|
@ -756,11 +791,35 @@ get_scope_prefix_when_not_defined(_Config) ->
|
||||||
?assertEqual(<<"rabbitmq.">>, rabbit_oauth2_config:get_scope_prefix()),
|
?assertEqual(<<"rabbitmq.">>, rabbit_oauth2_config:get_scope_prefix()),
|
||||||
?assertEqual(<<"rabbitmq2.">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)).
|
?assertEqual(<<"rabbitmq2.">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)).
|
||||||
|
|
||||||
|
get_empty_scope_prefix(_Config) ->
|
||||||
|
?assertEqual(<<"">>, rabbit_oauth2_config:get_scope_prefix()),
|
||||||
|
?assertEqual(<<"">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)).
|
||||||
|
|
||||||
get_scope_prefix(_Config) ->
|
get_scope_prefix(_Config) ->
|
||||||
?assertEqual(<<"some-prefix-">>, rabbit_oauth2_config:get_scope_prefix()),
|
?assertEqual(<<"some-prefix-">>, rabbit_oauth2_config:get_scope_prefix()),
|
||||||
?assertEqual(<<"my-prefix:">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq1">>)),
|
?assertEqual(<<"my-prefix:">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq1">>)),
|
||||||
?assertEqual(rabbit_oauth2_config:get_scope_prefix(), rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)).
|
?assertEqual(rabbit_oauth2_config:get_scope_prefix(), rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)).
|
||||||
|
|
||||||
|
get_scope_prefix_for_resource_one_returns_default_scope_prefix(_Config) ->
|
||||||
|
?assertEqual(undefined, application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix)),
|
||||||
|
?assertEqual(append_paths(?RABBITMQ_RESOURCE_ONE, <<".">>),
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)).
|
||||||
|
get_scope_prefix_for_resource_one_returns_root_scope_prefix(_Config) ->
|
||||||
|
{ok, Prefix} = application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
||||||
|
?assertEqual(rabbit_oauth2_config:get_scope_prefix(),
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)),
|
||||||
|
?assertEqual(Prefix,
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)).
|
||||||
|
get_scope_prefix_for_resource_one_returns_empty_scope_prefix(_Config) ->
|
||||||
|
?assertEqual(<<"">>,
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)).
|
||||||
|
get_scope_prefix_for_resource_two_returns_root_scope_prefix(_Config) ->
|
||||||
|
{ok, Prefix} = application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix),
|
||||||
|
?assertEqual(rabbit_oauth2_config:get_scope_prefix(),
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_TWO)),
|
||||||
|
?assertEqual(Prefix,
|
||||||
|
rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_TWO)).
|
||||||
|
|
||||||
get_resource_server_type_when_not_defined(_Config) ->
|
get_resource_server_type_when_not_defined(_Config) ->
|
||||||
?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type()),
|
?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type()),
|
||||||
?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)).
|
?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)).
|
||||||
|
|
|
@ -1270,7 +1270,7 @@ test_validate_payload_resource_server_id_mismatch(_) ->
|
||||||
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, EmptyAud, ?DEFAULT_SCOPE_PREFIX)).
|
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, EmptyAud, ?DEFAULT_SCOPE_PREFIX)).
|
||||||
|
|
||||||
test_validate_payload_with_scope_prefix(_) ->
|
test_validate_payload_with_scope_prefix(_) ->
|
||||||
Scenarios = [ { <<>>,
|
Scenarios = [ { <<"">>,
|
||||||
#{<<"aud">> => [?RESOURCE_SERVER_ID],
|
#{<<"aud">> => [?RESOURCE_SERVER_ID],
|
||||||
<<"scope">> => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]},
|
<<"scope">> => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]},
|
||||||
[<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]
|
[<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export IMPORT_DIR=deps/rabbitmq_management/selenium/test/authnz-msg-protocols/imports
|
|
|
@ -1,2 +0,0 @@
|
||||||
export DEVKEYCLOAK_URL=https://localhost:8442/realms/dev
|
|
||||||
export DEVKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/devkeycloak/ca_certificate.pem
|
|
|
@ -1,2 +0,0 @@
|
||||||
export PRODKEYCLOAK_URL=https://localhost:8443/realms/prod
|
|
||||||
export PRODKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/prodkeycloak/ca_certificate.pem
|
|
|
@ -1 +0,0 @@
|
||||||
export OAUTH_SERVER_CONFIG_BASEDIR=deps/rabbitmq_management/selenium/test
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
screens/*/*
|
||||||
|
logs
|
||||||
|
suites/logs/*
|
||||||
|
suites/screens/*
|
||||||
|
test/oauth/*/h2/*.trace.db
|
||||||
|
test/oauth/*/h2/*.lock.db
|
||||||
|
*/target/*
|
|
@ -4,7 +4,6 @@ FROM atools/jdk-maven-node:mvn3-jdk11-node16 as base
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
|
|
||||||
COPY package.json package.json
|
COPY package.json package.json
|
||||||
COPY run-amqp10-roundtriptest run-amqp10-roundtriptest
|
|
||||||
|
|
||||||
FROM base as test
|
FROM base as test
|
||||||
RUN npm install
|
RUN npm install
|
|
@ -1,7 +1,38 @@
|
||||||
# Automated End-to-End testing of the management ui with Selenium
|
# Automated End-to-End testing with Mocha and Selenium
|
||||||
|
|
||||||
Selenium webdriver is used to drive web browser's interactions on the management ui.
|
## What is it?
|
||||||
And Mocha is used as the testing framework for Javascript.
|
|
||||||
|
It is a solution that allows you to write end-to-end tests in Javascript. The solution
|
||||||
|
takes care of:
|
||||||
|
|
||||||
|
- generating the required RabbitMQ configuration
|
||||||
|
- deploying RabbitMQ with the generated configuration in 3 ways:
|
||||||
|
- from source via `make run-broker`.
|
||||||
|
- with docker via a single docker instance.
|
||||||
|
- with docker compose via a 3-node cluster.
|
||||||
|
- deploying any other dependencies required by the test case such as:
|
||||||
|
- keycloak
|
||||||
|
- uaa
|
||||||
|
- ldap
|
||||||
|
- http authentication backend
|
||||||
|
- http proxy
|
||||||
|
- http portal
|
||||||
|
- running the test cases
|
||||||
|
- capturing the logs from RabbitMQ and all the dependencies
|
||||||
|
- stopping RabbitMQ and all the dependencies
|
||||||
|
|
||||||
|
## Integration with Github actions
|
||||||
|
|
||||||
|
These are the three github workflows that run end-to-end tests:
|
||||||
|
- [test-management-ui.yaml](.github/workflows/test-management-ui.yaml) Runs all the test suites
|
||||||
|
listed on the file [short-suite-management-ui](selenium/short-suite-management-ui). It tests the management ui deployed a standalone RabbitMQ server. It is invoked on every push to a branch.
|
||||||
|
- [test-management-ui-for-prs.yaml](.github/workflows/test-management-ui.yaml) Runs all the test suites
|
||||||
|
listed on the file [full-suite-management-ui](selenium/full-suite-management-ui). It tests the management ui deployed on a 3-node cluster using a smaller test suite. It is invoked on every push to a PR.
|
||||||
|
- [test-authnz.yaml](.github/workflows/test-authnz.yaml) Runs all the test suites
|
||||||
|
listed on the file [full-suite-authnz-messaging](selenium/full-suite-authnz-messaging). It is invoked on every push to a PR and/or branch.
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
The following must be installed to run the tests:
|
The following must be installed to run the tests:
|
||||||
- make
|
- make
|
||||||
|
@ -10,9 +41,9 @@ The following must be installed to run the tests:
|
||||||
|
|
||||||
# Organization of test cases
|
# Organization of test cases
|
||||||
|
|
||||||
`test` folder contains the test cases written in Javascript using Selenium webdriver. Test cases are grouped into folders based on the area of functionality.
|
`test` folder contains the test cases written in Javascript using Mocha framework.
|
||||||
For instance, `test/basic-auth` contains test cases that validates basic authentication. Another example, a bit
|
Test cases are grouped into folders based on the area of functionality.
|
||||||
more complex, is `test/oauth` where the test cases are stored in subfolders. For instance, `test/oauth/with-sp-initiated` which validate OAuth 2 authorization where users come to RabbitMQ without any token and RabbitMQ initiates the authorization process.
|
For instance, `test/basic-auth` contains test cases that validates basic authentication. Another example, a bit more complex, is `test/oauth` where the test cases are stored in subfolders. For instance, `test/oauth/with-sp-initiated` which validate OAuth 2 authorization where users come to RabbitMQ without any token and RabbitMQ initiates the authorization process.
|
||||||
|
|
||||||
The `test` folder also contains the necessary configuration files. For instance, `test/basic-auth` contains `rabbitmq.conf` file which is also shared by other test cases such as `test/definitions` or `test/limits`.
|
The `test` folder also contains the necessary configuration files. For instance, `test/basic-auth` contains `rabbitmq.conf` file which is also shared by other test cases such as `test/definitions` or `test/limits`.
|
||||||
|
|
|
@ -7,10 +7,10 @@ if [[ -f "/code/amqp10-roundtriptest" ]]; then
|
||||||
echo "Running amqp10-roundtriptest inside mocha-test docker image ..."
|
echo "Running amqp10-roundtriptest inside mocha-test docker image ..."
|
||||||
java -jar /code/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar $@
|
java -jar /code/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar $@
|
||||||
else
|
else
|
||||||
if [[ ! -f "amqp10-roundtriptest/target/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar" ]]; then
|
if [[ ! -f "${SCRIPT}/target/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar" ]]; then
|
||||||
echo "Building amqp10-roundtriptest jar ..."
|
echo "Building amqp10-roundtriptest jar ..."
|
||||||
mvn -f amqp10-roundtriptest package $@
|
mvn -f amqp10-roundtriptest package $@
|
||||||
fi
|
fi
|
||||||
echo "Running amqp10-roundtriptest jar ..."
|
echo "Running amqp10-roundtriptest jar ..."
|
||||||
java -jar amqp10-roundtriptest/target/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar $@
|
java -jar ${SCRIPT}/target/amqp10-roundtriptest-1.0-SNAPSHOT-jar-with-dependencies.jar $@
|
||||||
fi
|
fi
|
|
@ -34,7 +34,7 @@ stop_rabbitmq() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
stop_local_rabbitmq() {
|
stop_local_rabbitmq() {
|
||||||
RABBITMQ_SERVER_ROOT=$(realpath $TEST_DIR/../../../../)
|
RABBITMQ_SERVER_ROOT=$(realpath ../)
|
||||||
gmake --directory=${RABBITMQ_SERVER_ROOT} stop-node
|
gmake --directory=${RABBITMQ_SERVER_ROOT} stop-node
|
||||||
}
|
}
|
||||||
save_logs_rabbitmq() {
|
save_logs_rabbitmq() {
|
||||||
|
@ -51,7 +51,7 @@ start_local_rabbitmq() {
|
||||||
|
|
||||||
init_rabbitmq
|
init_rabbitmq
|
||||||
|
|
||||||
RABBITMQ_SERVER_ROOT=$(realpath $TEST_DIR/../../../../)
|
RABBITMQ_SERVER_ROOT=$(realpath ../)
|
||||||
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
|
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
|
||||||
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
|
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ start_local_rabbitmq() {
|
||||||
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_ADVANCED_CONFIG
|
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_ADVANCED_CONFIG
|
||||||
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins /tmp/etc/rabbitmq/
|
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins /tmp/etc/rabbitmq/
|
||||||
RESULT=$?
|
RESULT=$?
|
||||||
|
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins /tmp/etc/rabbitmq/
|
||||||
if [ $RESULT -eq 0 ]; then
|
if [ $RESULT -eq 0 ]; then
|
||||||
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_ADVANCED_CONFIG"
|
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_ADVANCED_CONFIG"
|
||||||
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
|
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
|
||||||
|
@ -84,12 +85,10 @@ start_docker_cluster_rabbitmq() {
|
||||||
kill_container_if_exist rabbitmq2
|
kill_container_if_exist rabbitmq2
|
||||||
|
|
||||||
mkdir -p $CONF_DIR/rabbitmq
|
mkdir -p $CONF_DIR/rabbitmq
|
||||||
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
|
|
||||||
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
|
|
||||||
|
|
||||||
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
|
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
|
||||||
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
|
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
|
||||||
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /$CONF_DIR/rabbitmq/advanced.config
|
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/advanced.config
|
||||||
RESULT=$?
|
RESULT=$?
|
||||||
if [ $RESULT -eq 0 ]; then
|
if [ $RESULT -eq 0 ]; then
|
||||||
if [ -s $RESULT ]; then
|
if [ -s $RESULT ]; then
|
||||||
|
@ -98,8 +97,10 @@ start_docker_cluster_rabbitmq() {
|
||||||
rm $CONF_DIR/rabbitmq/advanced.config
|
rm $CONF_DIR/rabbitmq/advanced.config
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
mkdir -p $CONF_DIR/rabbitmq/conf.d/
|
if [ -f ${RABBITMQ_CONFIG_DIR}/logging.conf ]; then
|
||||||
cp ${RABBITMQ_CONFIG_DIR}/logging.conf $CONF_DIR/rabbitmq/conf.d/
|
mkdir -p $CONF_DIR/rabbitmq/conf.d/
|
||||||
|
cp ${RABBITMQ_CONFIG_DIR}/logging.conf $CONF_DIR/rabbitmq/conf.d/
|
||||||
|
fi
|
||||||
if [ -f ${RABBITMQ_CONFIG_DIR}/enabled_plugins ]; then
|
if [ -f ${RABBITMQ_CONFIG_DIR}/enabled_plugins ]; then
|
||||||
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins $CONF_DIR/rabbitmq
|
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins $CONF_DIR/rabbitmq
|
||||||
fi
|
fi
|
||||||
|
@ -136,25 +137,30 @@ start_docker_rabbitmq() {
|
||||||
kill_container_if_exist rabbitmq
|
kill_container_if_exist rabbitmq
|
||||||
|
|
||||||
mkdir -p $CONF_DIR/rabbitmq
|
mkdir -p $CONF_DIR/rabbitmq
|
||||||
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
|
|
||||||
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
|
|
||||||
|
|
||||||
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
|
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
|
||||||
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
|
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
|
||||||
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /$CONF_DIR/rabbitmq/advanced.config
|
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/advanced.config
|
||||||
RESULT=$?
|
RESULT=$?
|
||||||
if [ $RESULT -eq 0 ]; then
|
if [ $RESULT -eq 0 ]; then
|
||||||
print "> EFFECTIVE ADVANCED_CONFIG_FILE: $CONF_DIR/rabbitmq/advanced.config"
|
if [ -s $RESULT ]; then
|
||||||
EXTRA_MOUNTS="-v $CONF_DIR/rabbitmq/advanced.config:${MOUNT_ADVANCED_CONFIG}:ro "
|
print "> EFFECTIVE ADVANCED_CONFIG_FILE: $CONF_DIR/rabbitmq/advanced.config"
|
||||||
|
else
|
||||||
|
rm $CONF_DIR/rabbitmq/advanced.config
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -f ${RABBITMQ_CONFIG_DIR}/logging.conf ]; then
|
||||||
|
mkdir -p $CONF_DIR/rabbitmq/conf.d/
|
||||||
|
cp ${RABBITMQ_CONFIG_DIR}/logging.conf $CONF_DIR/rabbitmq/conf.d/
|
||||||
fi
|
fi
|
||||||
if [ -f ${RABBITMQ_CONFIG_DIR}/enabled_plugins ]; then
|
if [ -f ${RABBITMQ_CONFIG_DIR}/enabled_plugins ]; then
|
||||||
EXTRA_MOUNTS="$EXTRA_MOUNTS -v ${RABBITMQ_CONFIG_DIR}/enabled_plugins:/etc/rabbitmq/enabled_plugins "
|
cp ${RABBITMQ_CONFIG_DIR}/enabled_plugins $CONF_DIR/rabbitmq
|
||||||
fi
|
fi
|
||||||
if [ -d ${RABBITMQ_CONFIG_DIR}/certs ]; then
|
if [ -d ${RABBITMQ_CONFIG_DIR}/certs ]; then
|
||||||
EXTRA_MOUNTS=" $EXTRA_MOUNTS -v ${RABBITMQ_CONFIG_DIR}/certs:/var/rabbitmq/certs "
|
cp -r ${RABBITMQ_CONFIG_DIR}/certs $CONF_DIR/rabbitmq
|
||||||
fi
|
fi
|
||||||
if [ -d ${RABBITMQ_CONFIG_DIR}/imports ]; then
|
if [ -d ${RABBITMQ_CONFIG_DIR}/imports ]; then
|
||||||
EXTRA_MOUNTS="$EXTRA_MOUNTS -v ${RABBITMQ_CONFIG_DIR}/imports:/var/rabbitmq/imports "
|
cp -r ${RABBITMQ_CONFIG_DIR}/imports $CONF_DIR/rabbitmq
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print "> RABBITMQ_TEST_DIR: /var/rabbitmq"
|
print "> RABBITMQ_TEST_DIR: /var/rabbitmq"
|
||||||
|
@ -167,10 +173,9 @@ start_docker_rabbitmq() {
|
||||||
-p 5671:5671 \
|
-p 5671:5671 \
|
||||||
-p 15672:15672 \
|
-p 15672:15672 \
|
||||||
-p 15671:15671 \
|
-p 15671:15671 \
|
||||||
-v ${RABBITMQ_CONFIG_DIR}/logging.conf:/etc/rabbitmq/conf.d/logging.conf:ro \
|
-v $CONF_DIR/rabbitmq/:/etc/rabbitmq \
|
||||||
-v $CONF_DIR/rabbitmq/rabbitmq.conf:${MOUNT_RABBITMQ_CONF}:ro \
|
-v $CONF_DIR/rabbitmq/:/var/rabbitmq \
|
||||||
-v ${TEST_DIR}:/config \
|
-v ${TEST_DIR}:/config \
|
||||||
${EXTRA_MOUNTS} \
|
|
||||||
${RABBITMQ_DOCKER_IMAGE}
|
${RABBITMQ_DOCKER_IMAGE}
|
||||||
|
|
||||||
wait_for_message rabbitmq "Server startup complete"
|
wait_for_message rabbitmq "Server startup complete"
|
|
@ -1,6 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#set -x
|
if [[ ! -z "${DEBUG}" ]]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
SUITE=$(caller)
|
SUITE=$(caller)
|
||||||
|
@ -396,6 +399,9 @@ run_local_with() {
|
||||||
if [[ "$COMMAND" == "start-rabbitmq" ]]
|
if [[ "$COMMAND" == "start-rabbitmq" ]]
|
||||||
then
|
then
|
||||||
start_local_rabbitmq
|
start_local_rabbitmq
|
||||||
|
elif [[ "$COMMAND" == "stop-rabbitmq" ]]
|
||||||
|
then
|
||||||
|
stop_local_rabbitmq
|
||||||
elif [[ "$COMMAND" == "start-others" ]]
|
elif [[ "$COMMAND" == "start-others" ]]
|
||||||
then
|
then
|
||||||
start_local_others
|
start_local_others
|
|
@ -6,7 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fakeportal": "node fakeportal/app.js",
|
"fakeportal": "node fakeportal/app.js",
|
||||||
"fakeproxy": "node fakeportal/proxy.js",
|
"fakeproxy": "node fakeportal/proxy.js",
|
||||||
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest",
|
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) && amqp10-roundtriptest/run",
|
||||||
"test": " eval $(cat $ENV_FILE ) && mocha --recursive --trace-warnings --timeout 40000"
|
"test": " eval $(cat $ENV_FILE ) && mocha --recursive --trace-warnings --timeout 40000"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
|
@ -12,5 +12,4 @@
|
||||||
rabbitmq_shovel_management,rabbitmq_stomp,rabbitmq_stream,
|
rabbitmq_shovel_management,rabbitmq_stomp,rabbitmq_stream,
|
||||||
rabbitmq_stream_common,rabbitmq_stream_management,rabbitmq_top,
|
rabbitmq_stream_common,rabbitmq_stream_management,rabbitmq_top,
|
||||||
rabbitmq_tracing,rabbitmq_trust_store,rabbitmq_web_dispatch,
|
rabbitmq_tracing,rabbitmq_trust_store,rabbitmq_web_dispatch,
|
||||||
rabbitmq_web_mqtt,rabbitmq_web_mqtt_examples,rabbitmq_web_stomp,
|
rabbitmq_web_mqtt,rabbitmq_web_stomp].
|
||||||
rabbitmq_web_stomp_examples].
|
|
|
@ -0,0 +1 @@
|
||||||
|
export IMPORT_DIR=test/authnz-msg-protocols/imports
|
|
@ -0,0 +1,2 @@
|
||||||
|
export DEVKEYCLOAK_URL=https://localhost:8442/realms/dev
|
||||||
|
export DEVKEYCLOAK_CA_CERT=test/multi-oauth/devkeycloak/ca_certificate.pem
|
|
@ -0,0 +1,2 @@
|
||||||
|
export PRODKEYCLOAK_URL=https://localhost:8443/realms/prod
|
||||||
|
export PRODKEYCLOAK_CA_CERT=test/multi-oauth/prodkeycloak/ca_certificate.pem
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue