Compare commits

..
Author SHA1 Message Date
yp c18f7773e3 test: remove xray settings ui tests (#3338) 2026-10-27 23:22:32 +09:00
22 changed files with 92 additions and 3785 deletions

No files matched your search

-2
View File
@@ -1053,7 +1053,6 @@ jobs:
- XrayGeneratorMatrixTest
- XrayConfigModelTest
- XrayCoreAcceptsTest
- XrayPagesUiTest
- UiLanguageModelAndControllerTest
- UiIpModelAndControllerTest
- UiAppSTModelAndControllerTest
@@ -1271,7 +1270,6 @@ jobs:
- XrayGeneratorMatrixTest
- XrayConfigModelTest
- XrayCoreAcceptsTest
- XrayPagesUiTest
- UiLanguageModelAndControllerTest
- UiIpModelAndControllerTest
- UiAppSTModelAndControllerTest
-219
View File
@@ -1,219 +0,0 @@
# AGENTS.md
Operating guide for this repository. Read it before changing anything.
## Project Overview
AmneziaVPN is a cross-platform VPN client built with C++ and Qt Quick (QML) for
Windows, macOS, Linux, Android and iOS. It has three kinds of servers, each
with its own config model (`serverConfigUtils::ConfigType`):
- **Self-hosted.** The admin connects the app to their own server over SSH; the
app installs protocols as Docker containers (AmneziaWG, WireGuard, OpenVPN,
XRay, IPsec and others) and generates client configs (`SelfHostedAdmin`).
Access is shared with other users as `vpn://` keys, which import without
server management rights (`SelfHostedUser`).
- **Amnezia Premium and Free.** Configs come from the Amnezia gateway API. The
gateway passes the backend's config through; the client does not invent
server-side values.
- **Native protocol configs.** Third-party configs imported as they are:
WireGuard and AmneziaWG `.conf`, OpenVPN `.ovpn`, XRay and Shadowsocks links
(`Native`). The app only connects with them; there is no server to manage.
A change to shared code (protocol configs, import, connection) must keep
working for all three.
The desktop builds run a privileged service next to the GUI; the GUI talks to
it over Qt Remote Objects. Android, iOS and the macOS App Store build
(`MACOS_NE`) run the tunnel in the platform's VPN extension instead.
## Repository Layout
- `client/core/` — business logic with no UI: controllers, configurators
(client config generation per protocol), installers (server-side setup over
SSH), protocol and server config models, repositories (encrypted settings
storage).
- `client/ui/controllers/` — `*UiController` classes exposed to QML. Thin
adapters over `core/`: they translate between QML and core types and hold no
business logic of their own.
- `client/ui/models/` — list and property models for QML.
- `client/ui/qml/` — the interface:
- `Pages2/` — screens, one file per page.
- `Components/` — composite pieces of screens.
- `Controls2/` — design system controls, named `<Name>Type.qml`; text styles
live in `Controls2/TextTypes/`.
- `Modules/Style/AmneziaStyle.qml` — design tokens (colours and the like).
- `Config/` — `GlobalConfig`, `Dialogs` and other singletons.
- `client/platforms/`, `client/android/`, `client/ios/`, `client/macos/` —
platform code: VPN extensions, Kotlin, Swift, Objective-C++.
- `client/daemon/`, `client/mozilla/`, `service/`, `ipc/` — the privileged
service and its IPC.
- `client/server_scripts/` — the shell scripts and Dockerfiles run on the
user's server.
- `client/translations/` — Qt `.ts` files.
- `client/tests/` — Qt Test suites, run through `ctest`.
- `recipes/` — Conan recipes for native dependencies (protocol libraries,
OpenSSL, libssh and others).
- `deploy/` — build and installer scripts. `.github/workflows/` — CI.
- `client/3rd/` — vendored third-party code. Do not edit it.
## Essential Commands
Run from the repository root.
```bash
deploy/build.sh -h # build options
deploy/build.sh # build for the host platform
deploy/build.sh -t android --aab # Android APK and AAB
cmake -S . -B build -DCMAKE_PREFIX_PATH=<Qt>/<platform> -DAMNEZIA_BUILD_TESTS=ON
cmake --build build --parallel
ctest --test-dir build/client/tests --output-on-failure
```
Qt 6.10 or later and Conan are required; native dependencies are built by Conan
from `recipes/` on the first configure.
## Repository Rules
These three come first. Each one has been asked for repeatedly.
### 1. Clean, self-documenting code, no comments
- Names and structure carry the meaning. Code must be readable without help.
- No explanatory comments in code: no `//` notes in QML or C++, no doc blocks.
- The one exception is a hack that looks wrong on purpose: a workaround for a
platform or Qt bug, an ordering that must not be "fixed". Then a short comment
next to it says why.
- Explanations of a change go into the chat summary or the commit message, not
into the code.
- Leave existing comments alone unless the change removes the code they
describe.
### 2. Reusable code (DRY), without abstraction for its own sake
- **QML.** Every reusable component lives in its own file. A piece of a page
goes into its own file in `Components/` even when it is used once, if that
keeps the page short and readable. No inline `component` definitions or
`Component` blocks inside pages. Before writing a new control, look in
`Controls2/` and `Components/` for one that already does the job, and extend
it if it almost does.
- **C++.** Logic shared by several places is extracted once and reused:
duplicated parsing, validation or config building is a bug waiting to diverge.
- **No fanaticism.** Do not create a base class, interface, helper or file
that only renames a single call, has one trivial user, or exists "for the
future". Decompose when it makes the code shorter, clearer or removes
duplication; otherwise keep it direct.
### 3. Do not add what was not asked for
- Implement exactly the request. No extra screens, buttons, settings,
refactors, renames, translations or "while I'm here" fixes.
- Ideas, improvements and problems noticed along the way are reported to the
user as suggestions. Nothing is implemented without the user agreeing first.
- When a request is ambiguous, ask instead of guessing big.
- Keep the diff small. If a fix grows beyond the obvious change, say so and
explain why before making it.
### Other rules
- **Committing is the user's call.** Prepare the change and a commit message;
do not commit or push unless asked. Commit messages use a conventional prefix
with a scope (`fix(ui): …`, `feat(macos): …`, `chore: …`): a one-line
subject plus one or two short body lines saying what was wrong and what
changed. Pull requests target `dev`.
- **Format C++ with `.clang-format`.** Format the lines you touch; do not
reformat files the change does not otherwise touch.
- **Delete unused code** instead of keeping it "for later".
- **Do not edit `client/3rd/`.** Fix vendored code through its upstream or a
Conan recipe.
## UI And Design System
- **Figma is the source of truth.** Screens follow the "Amnezia APP VPN" file;
components follow the "Amnezia DS Library". Where the two disagree, the app
screens win.
- **Build UI from the design system controls** in `Controls2/`
(`ButtonType`, `ListItemType`, `TextFieldType`, `SheetType`,
`NavigationBarType` and the others) and the text types in
`Controls2/TextTypes/`. Do not style raw Qt Quick Controls on a page.
- **No hex colours in QML.** Every colour is a semantic token from
`AmneziaStyle.color`. A colour the tokens do not have is added to
`AmneziaStyle` first, with the name the design system gives it.
- **Icons come from Figma**, exported from the DS Library. Never draw an icon
by hand. Exports include page background rectangles — strip them and keep
the `0 0 24 24` viewBox.
- **Restyling a component never redesigns a screen.** A design system update
changes components; screen layout and behaviour stay unless the user asks.
- **No Qt5Compat.GraphicalEffects.** It overflows the interpreted JS stack on
iOS. Use `QtQuick.Effects` (`MultiEffect`), and create effects only while
they are visible: each instance is expensive to create.
### QML pitfalls
- A vertical `ListView` resets the delegate's `x`. Put insets on the
ListView's `leftMargin`/`rightMargin`, or on a child inside the delegate.
- `lineHeightMode: Text.FixedHeight` puts all the extra leading below the text,
while Figma splits it evenly. The text types already compensate with
`halfLeading` padding; a new text type must do the same.
- QML is loaded from `qml.qrc`, not compiled ahead of time: a QML error shows
up only at runtime. When moving QML between files, compare the `import`
lines — types registered from C++ under their own URIs fail only when the
page is opened.
## Translations
- Every user-visible string goes through `qsTr()` in QML or `tr()` in C++.
- The `.ts` files contain only finished, non-empty translations: no
`vanished`, `obsolete` or `unfinished` entries, and every language holds the
same set of strings. `lrelease` must report `N finished and 0 unfinished` for
every file.
- Refresh `.ts` files only when the task is about translations. `lupdate` run
through `qt6_add_translations` misses QML, so run it over an explicit file
list that skips `client/3rd` (from `client/translations`):
```bash
find .. -path ../3rd -prune -o -path ../3rd-prebuilt -prune -o \
\( -name '*.qml' -o -name '*.cpp' -o -name '*.h' -o -name '*.mm' \) -print \
| sort > /tmp/src.lst
<Qt>/bin/lupdate @/tmp/src.lst -ts amneziavpn_*.ts
```
- `lupdate` output differs between macOS and Windows (keychain strings swap
places), so a large `.ts` diff after a refresh is often only that.
- Technical terms stay in English in every language: protocol parameter names
and their descriptions (`Jc`, `S1`, `H1`…), config keys, transport and
algorithm names. Ordinary UI words are translated.
- Renaming a QML file changes its translation context, so its strings have to
be carried over.
## Which Check When
- **Touched C++** — build the affected targets, and run `ctest` when the change
touches anything the tests cover (servers, imports, configs, settings,
split tunneling, XRay).
- **Touched QML** — build, then open every page you changed. If the app cannot
be launched, at least load the changed files through the QML engine so that
syntax, imports and type errors surface, and say what was not checked.
- **Touched a protocol config, import or connection path** — check self-hosted,
Premium/Free and native configs, and every platform that consumes the field
(desktop service, Android, iOS/macOS NE).
- **Touched CMake, recipes or the app icon** — reconfigure from a clean build
directory as well: branding paths are CMake cache variables and keep stale
values in an existing build directory.
- **Touched translations** — `lrelease` on every `.ts` file, see above.
## Privacy
- Never commit or repeat private keys, `vpn://` keys, server addresses,
passwords or tokens — including ones found in pasted configs, logs or test
output.
- Logs must not contain secrets or full config bodies.
- Local absolute paths (`/Users/...`, `C:\...`) do not belong in repository
files.
## Verification Culture
- Verify your own work before reporting it. "Should work" is not a result;
say exactly what was built, run or opened, and what was not.
- Hypotheses are settled by an experiment, not by reasoning about the code.
- After a wrong diagnosis, remove the code it produced.
-6
View File
@@ -1,6 +0,0 @@
# CLAUDE.md
The operating guide for this repository lives in [AGENTS.md](AGENTS.md), so
every agent runtime reads the same file.
@AGENTS.md
+6 -32
View File
@@ -1,5 +1,7 @@
function(apple_icon_composer_supported OUT_VAR)
set(${OUT_VAR} FALSE PARENT_SCOPE)
function(check_apple_app_icon ICON_PATH)
if(NOT EXISTS "${ICON_PATH}/icon.json")
message(FATAL_ERROR "App icon not found: ${ICON_PATH} (expected an Icon Composer .icon bundle)")
endif()
execute_process(
COMMAND xcrun actool --version --output-format human-readable-text
@@ -8,7 +10,7 @@ function(apple_icon_composer_supported OUT_VAR)
)
string(REGEX MATCH "short-bundle-version: ([0-9]+)" ACTOOL_VERSION_MATCH "${ACTOOL_VERSION_OUTPUT}")
if(NOT ACTOOL_VERSION_MATCH OR CMAKE_MATCH_1 LESS 26)
return()
message(FATAL_ERROR "Xcode 26 or later is required to build the app icon ${ICON_PATH}")
endif()
execute_process(
@@ -18,34 +20,6 @@ function(apple_icon_composer_supported OUT_VAR)
ERROR_QUIET
)
if(HOST_MACOS_VERSION VERSION_LESS 26)
return()
endif()
set(${OUT_VAR} TRUE PARENT_SCOPE)
endfunction()
function(check_apple_app_icon ICON_PATH)
if(NOT EXISTS "${ICON_PATH}/icon.json")
message(FATAL_ERROR "App icon not found: ${ICON_PATH} (expected an Icon Composer .icon bundle)")
endif()
apple_icon_composer_supported(ICON_COMPOSER_SUPPORTED)
if(NOT ICON_COMPOSER_SUPPORTED)
message(FATAL_ERROR "Xcode 26 and macOS 26 or later are required to build the app icon ${ICON_PATH}")
message(FATAL_ERROR "macOS 26 or later is required to build the app icon ${ICON_PATH} (found macOS ${HOST_MACOS_VERSION})")
endif()
endfunction()
function(select_macos_app_icon ICON_PATH ICNS_PATH OUT_USE_ICON_COMPOSER)
apple_icon_composer_supported(ICON_COMPOSER_SUPPORTED)
if(ICON_COMPOSER_SUPPORTED)
check_apple_app_icon(${ICON_PATH})
set(${OUT_USE_ICON_COMPOSER} TRUE PARENT_SCOPE)
return()
endif()
if(NOT EXISTS "${ICNS_PATH}")
message(FATAL_ERROR "Xcode 26 and macOS 26 are required for ${ICON_PATH}, and the fallback icon ${ICNS_PATH} was not found")
endif()
message(STATUS "Icon Composer needs Xcode 26 and macOS 26, using the fallback app icon ${ICNS_PATH}")
set(${OUT_USE_ICON_COMPOSER} FALSE PARENT_SCOPE)
endfunction()
-3
View File
@@ -14,9 +14,6 @@ endif()
if(NOT CLIENT_MACOS_APP_ICON_PATH)
set(CLIENT_MACOS_APP_ICON_PATH "${_CLIENT_SRC_DIR}/images/AppIcon.icon" CACHE PATH "macOS app icon (Icon Composer .icon)")
endif()
if(NOT CLIENT_MACOS_APP_ICNS_PATH)
set(CLIENT_MACOS_APP_ICNS_PATH "${_CLIENT_SRC_DIR}/macos/app/AppIcon.icns" CACHE FILEPATH "macOS app icon (.icns) for builds without Xcode 26 and macOS 26")
endif()
if(NOT CLIENT_MACOS_EXTRA_PLIST_ENTRIES)
set(CLIENT_MACOS_EXTRA_PLIST_ENTRIES "" CACHE STRING "Extra macOS Info.plist entries appended verbatim (e.g. appearance pinning)")
+25 -32
View File
@@ -34,43 +34,36 @@ set(SOURCES ${SOURCES}
select_macos_app_icon(${CLIENT_MACOS_APP_ICON_PATH} ${CLIENT_MACOS_APP_ICNS_PATH} USE_ICON_COMPOSER)
check_apple_app_icon(${CLIENT_MACOS_APP_ICON_PATH})
set(APP_ICON_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/appicon)
set(APP_ICON_FILES ${APP_ICON_OUTPUT_DIR}/Assets.car ${APP_ICON_OUTPUT_DIR}/AppIcon.icns)
file(GLOB_RECURSE APP_ICON_INPUTS CONFIGURE_DEPENDS "${CLIENT_MACOS_APP_ICON_PATH}/*")
add_custom_command(
OUTPUT ${APP_ICON_FILES}
COMMAND ${CMAKE_COMMAND} -E make_directory ${APP_ICON_OUTPUT_DIR}
COMMAND xcrun actool --output-format human-readable-text --warnings --errors
--platform macosx
--minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET}
--app-icon AppIcon
--output-partial-info-plist ${APP_ICON_OUTPUT_DIR}/partial.plist
--compile ${APP_ICON_OUTPUT_DIR}
${CLIENT_MACOS_APP_ICON_PATH}
DEPENDS ${APP_ICON_INPUTS}
COMMENT "Compiling macOS app icon"
VERBATIM
)
set(MACOSX_BUNDLE_ICON_FILE AppIcon)
if(USE_ICON_COMPOSER)
set(APP_ICON_FILES ${APP_ICON_OUTPUT_DIR}/Assets.car ${APP_ICON_OUTPUT_DIR}/AppIcon.icns)
file(GLOB_RECURSE APP_ICON_INPUTS CONFIGURE_DEPENDS "${CLIENT_MACOS_APP_ICON_PATH}/*")
add_custom_command(
OUTPUT ${APP_ICON_FILES}
COMMAND ${CMAKE_COMMAND} -E make_directory ${APP_ICON_OUTPUT_DIR}
COMMAND xcrun actool --output-format human-readable-text --warnings --errors
--platform macosx
--minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET}
--app-icon AppIcon
--output-partial-info-plist ${APP_ICON_OUTPUT_DIR}/partial.plist
--compile ${APP_ICON_OUTPUT_DIR}
${CLIENT_MACOS_APP_ICON_PATH}
DEPENDS ${APP_ICON_INPUTS}
COMMENT "Compiling macOS app icon"
VERBATIM
)
set_source_files_properties(${APP_ICON_FILES} PROPERTIES GENERATED TRUE)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND plutil -replace CFBundleIconName -string AppIcon $<TARGET_BUNDLE_CONTENT_DIR:${PROJECT}>/Info.plist
VERBATIM
)
else()
set(APP_ICON_FILES ${APP_ICON_OUTPUT_DIR}/AppIcon.icns)
configure_file(${CLIENT_MACOS_APP_ICNS_PATH} ${APP_ICON_FILES} COPYONLY)
endif()
set_source_files_properties(${APP_ICON_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set_source_files_properties(${APP_ICON_FILES} PROPERTIES GENERATED TRUE MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${APP_ICON_FILES})
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND plutil -replace CFBundleIconName -string AppIcon $<TARGET_BUNDLE_CONTENT_DIR:${PROJECT}>/Info.plist
VERBATIM
)
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
+8 -15
View File
@@ -122,24 +122,17 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
select_macos_app_icon(${CLIENT_MACOS_APP_ICON_PATH} ${CLIENT_MACOS_APP_ICNS_PATH} USE_ICON_COMPOSER)
check_apple_app_icon(${CLIENT_MACOS_APP_ICON_PATH})
if(USE_ICON_COMPOSER)
set(APP_ICON_SOURCE ${CLIENT_MACOS_APP_ICON_PATH})
set_source_files_properties(
${APP_ICON_SOURCE}
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
XCODE_LAST_KNOWN_FILE_TYPE folder.iconcomposer.icon
)
else()
set(APP_ICON_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/appicon/AppIcon.icns)
configure_file(${CLIENT_MACOS_APP_ICNS_PATH} ${APP_ICON_SOURCE} COPYONLY)
set_source_files_properties(${APP_ICON_SOURCE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
endif()
set_source_files_properties(
${CLIENT_MACOS_APP_ICON_PATH}
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
XCODE_LAST_KNOWN_FILE_TYPE folder.iconcomposer.icon
)
target_sources(${PROJECT} PRIVATE
${APP_ICON_SOURCE}
${CLIENT_MACOS_APP_ICON_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
+41 -31
View File
@@ -1,35 +1,45 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg x="94.969" y="72.212" width="840" height="809.053" viewBox="0 0 840 809.053" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_5470_4284" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="841" height="810">
<path d="M838.697 289.781C839.128 290.057 839.092 290.7 838.633 290.929C835.038 292.719 831.485 294.6 827.806 296.232C799.555 308.776 772.521 323.383 749.551 344.372C741.699 351.545 734.598 359.689 727.949 368.025C721.643 375.923 719.536 385.597 719.631 395.64C719.651 397.39 719.936 399.14 720.232 401.108C720.434 402.455 721.585 403.47 722.947 403.47H754.934C756.426 403.47 757.641 404.682 757.573 406.173C757.006 418.719 752.651 429.513 745.064 438.533C734.995 450.489 730.891 464.7 726.144 478.966C719.221 499.763 718.756 521.202 720.398 542.478C722.108 564.655 713.899 582.337 700.656 598.83C690.259 611.783 680.19 625.011 669.847 638.019C659.244 651.355 655.291 666.414 656.426 683.344C657.89 705.043 662.979 725.963 667.727 747.088C671.598 764.332 672.871 782.151 675.21 799.723C675.317 800.514 675.272 801.319 675.174 802.406C675.006 804.292 672.993 805.496 671.237 804.786C667.425 803.244 663.961 801.983 661.064 799.914C647.862 790.5 639.736 777.081 633.087 762.665C629.836 755.612 626.686 748.509 623.528 741.499C623.236 740.851 622.7 740.346 622.038 740.086C612.637 736.39 610.069 734.129 607.429 727.667C607.012 726.646 606.042 725.945 604.94 725.931C590.847 725.747 576.823 727.121 563.328 731.798C539.442 740.065 515.514 748.249 491.969 757.391C465.73 767.585 438.573 769.648 411.239 766.355C385.068 763.198 359.197 757.514 333.176 753.1C324.448 751.625 315.61 750.75 306.841 749.425C304.666 749.097 302.504 748.249 300.507 747.279C284.746 739.6 269.178 731.511 253.267 724.159C236.015 716.193 222.69 721.877 216.301 739.559C213.675 746.828 212.115 754.467 209.57 761.764C205.685 772.9 201.622 784.009 197.148 794.913C193.962 802.692 187.887 806.658 178.915 807.795C177.278 808.003 175.934 806.595 176.072 804.951C176.687 797.655 177.055 790.615 178.241 783.722C180.676 769.607 184.781 755.724 186.272 741.527C187.749 727.343 187.024 712.913 186.86 698.607C186.833 696.53 184.918 694.125 183.289 692.458C171.62 680.597 159.95 668.736 147.993 657.176C135.338 644.933 123.039 632.28 114.585 616.812C106.896 602.751 94.8842 600.743 81.5728 603.339C60.1486 607.52 38.9845 612.959 17.7108 617.905C12.3942 619.143 7.11331 620.525 1.82366 621.848C1.1673 622.013 0.484093 621.706 0.169055 621.108C-0.123688 620.551 -0.0299188 619.87 0.401333 619.413C2.93984 616.719 5.31074 614.184 8.16162 611.811C24.2639 598.42 40.7356 585.452 56.6464 571.842C66.1408 563.712 74.9923 554.803 83.7754 545.894C85.5402 544.104 86.6484 540.674 86.4705 538.105C82.0653 474.457 96.8679 414.73 123.327 357.407C134.518 333.152 146.142 309.094 157.878 284.322C158.345 283.335 159.369 282.751 160.46 282.687C170.461 282.106 171.998 271.978 175.957 264.599C178.669 259.543 180.643 254.095 182.891 248.386C183.584 246.625 185.831 246.144 187.204 247.447C188.931 249.086 190.564 250.638 192.338 252.327C193.394 253.332 195.059 253.317 196.13 252.327C199.595 249.128 203.201 246.202 205.753 242.545C215.658 228.389 225.399 214.096 234.415 199.379C238.574 192.588 243.198 188.325 251.16 186.097C277.787 178.649 304.16 170.335 330.424 162.392C331.068 162.197 331.64 161.784 332.011 161.223C332.138 161.03 332.228 160.859 332.246 160.682C334.189 145.022 335.461 143.71 351.303 141.114C368.172 138.354 378.802 129.336 381.647 112.282C384.479 95.2702 385.314 77.9165 386.969 60.6994C388.351 46.2015 389.377 31.6626 391.087 17.2057C392.145 8.24311 394.608 3.97571 400.598 0.355299C401.894 -0.42824 403.557 0.142784 404.243 1.49332C407.282 7.4772 410.711 13.3289 412.744 19.6243C418.257 36.7184 422.635 54.1678 427.998 71.3166C432.608 86.0468 437.465 100.736 443.047 115.125C450.517 134.405 465.798 142.576 485.854 142.918C493.611 143.055 501.368 143.328 509.111 142.986C518.592 142.576 527.403 144.653 534.995 150.092C555.53 164.822 576.257 179.32 596.012 195.047C610.924 206.928 624.546 220.435 639.396 233.918C640.083 234.542 641.043 234.751 641.954 234.576C649.443 233.136 656.045 237.059 662.73 241.666C663.822 242.419 664.193 243.86 663.612 245.053C661.428 249.534 659.268 253.957 656.99 258.64C656.297 260.065 656.98 261.773 658.432 262.408C673.511 269.004 684.03 280.872 695.731 291.217C699.822 294.825 704.213 298.118 708.55 301.438C716.718 307.697 725.76 308.995 735.693 306.494C754.901 301.657 774.095 296.751 793.439 292.556C808.443 289.3 822.647 287.5 838.432 289.683C838.526 289.696 838.617 289.729 838.697 289.781ZM432.82 707.072C432.825 707.068 432.831 707.065 432.837 707.065C448.553 707.065 464.269 707.243 479.985 706.956C484.185 706.874 488.809 706.259 492.489 704.401C512.19 694.466 532.013 684.724 551.07 673.656C570.73 662.246 572.385 656.575 566.557 634.343C561.304 614.325 551.604 596.22 541.343 578.511C529.208 557.55 519.741 536.083 519.139 511.268C518.825 498.246 515.076 485.757 508.879 474.074C503.393 463.73 494.897 458.838 483.446 458.688C451.775 458.278 420.761 462.063 390.595 472.27C312.614 498.683 265.114 562.824 263.801 642.911C263.486 662.232 271.859 675.35 288.755 683.002C300.534 688.345 312.806 692.786 325.173 696.585C360.261 707.365 396.485 708.363 432.804 707.079C432.81 707.079 432.Line truncated
<g id="AppIcon / macOS / source 1024" clip-path="url(#clip0_5470_4251)">
<g id="01-mark">
<mask id="mask0_5470_4251" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="177" y="175" width="670" height="646">
<path id="Vector" d="M846.215 406.917C846.56 407.138 846.531 407.651 846.164 407.833C843.297 409.261 840.463 410.762 837.528 412.063C814.995 422.068 793.433 433.719 775.111 450.46C768.848 456.182 763.185 462.678 757.881 469.326C752.851 475.626 751.17 483.342 751.247 491.353C751.263 492.749 751.49 494.145 751.726 495.714C751.887 496.788 752.805 497.598 753.891 497.598H779.404C780.594 497.598 781.564 498.565 781.51 499.754C781.058 509.761 777.584 518.371 771.532 525.565C763.501 535.101 760.227 546.436 756.441 557.815C750.919 574.403 750.548 591.503 751.858 608.473C753.222 626.162 746.675 640.265 736.112 653.42C727.819 663.753 719.787 674.303 711.538 684.679C703.081 695.316 699.927 707.327 700.833 720.83C702.001 738.138 706.06 754.824 709.846 771.674C712.935 785.428 713.949 799.641 715.815 813.657C715.901 814.287 715.864 814.929 715.787 815.796C715.653 817.301 714.047 818.261 712.646 817.695C709.606 816.465 706.843 815.459 704.532 813.809C694.002 806.3 687.52 795.597 682.217 784.099C679.625 778.473 677.112 772.808 674.593 767.216C674.36 766.699 673.932 766.297 673.405 766.089C665.906 763.141 663.858 761.337 661.752 756.183C661.42 755.369 660.646 754.81 659.766 754.798C648.525 754.652 637.34 755.748 626.577 759.478C607.524 766.072 588.439 772.6 569.659 779.892C548.73 788.022 527.07 789.668 505.267 787.041C484.393 784.524 463.758 779.99 443.003 776.469C436.041 775.292 428.992 774.595 421.997 773.538C420.262 773.276 418.538 772.6 416.945 771.826C404.374 765.701 391.957 759.249 379.266 753.385C365.506 747.031 354.877 751.565 349.781 765.669C347.686 771.467 346.442 777.559 344.413 783.379C341.314 792.262 338.073 801.123 334.505 809.82C331.963 816.025 327.118 819.188 319.961 820.095C318.656 820.26 317.583 819.138 317.694 817.827C318.185 812.007 318.478 806.392 319.424 800.894C321.366 789.635 324.64 778.562 325.829 767.238C327.008 755.925 326.43 744.416 326.299 733.004C326.277 731.348 324.749 729.43 323.451 728.1C314.143 718.64 304.835 709.179 295.298 699.959C285.204 690.193 275.394 680.101 268.65 667.763C262.518 656.548 252.937 654.946 242.32 657.017C225.231 660.352 208.35 664.69 191.382 668.635C187.141 669.623 182.929 670.725 178.71 671.781C178.187 671.912 177.642 671.667 177.39 671.19C177.157 670.746 177.232 670.203 177.576 669.838C179.6 667.69 181.492 665.667 183.765 663.774C196.609 653.093 209.747 642.75 222.438 631.895C230.011 625.41 237.071 618.304 244.076 611.198C245.484 609.77 246.368 607.034 246.226 604.985C242.712 554.218 254.519 506.579 275.623 460.858C284.55 441.511 293.821 422.322 303.182 402.563C303.555 401.776 304.371 401.31 305.241 401.26C313.219 400.796 314.444 392.717 317.602 386.832C319.765 382.799 321.34 378.454 323.132 373.9C323.685 372.496 325.478 372.112 326.573 373.151C327.95 374.459 329.253 375.696 330.668 377.043C331.511 377.845 332.838 377.833 333.693 377.044C336.456 374.492 339.332 372.158 341.368 369.241C349.269 357.95 357.038 346.549 364.229 334.811C367.546 329.395 371.235 325.994 377.585 324.218C398.823 318.277 419.859 311.645 440.808 305.309C441.322 305.154 441.778 304.825 442.074 304.377C442.175 304.223 442.246 304.087 442.261 303.945C443.811 291.455 444.825 290.409 457.462 288.338C470.916 286.137 479.395 278.943 481.664 265.341C483.923 251.772 484.589 237.931 485.909 224.198C487.011 212.634 487.83 201.038 489.194 189.507C490.038 182.358 492.002 178.954 496.78 176.066C497.814 175.441 499.14 175.897 499.687 176.974C502.111 181.747 504.846 186.414 506.468 191.436C510.865 205.07 514.357 218.988 518.635 232.666C522.312 244.415 526.186 256.132 530.638 267.608C536.596 282.987 548.785 289.504 564.782 289.777C570.969 289.886 577.156 290.104 583.332 289.831C590.894 289.504 597.922 291.161 603.978 295.499C620.357 307.248 636.888 318.812 652.645 331.356C664.54 340.833 675.405 351.606 687.249 362.36C687.798 362.858 688.563 363.024 689.29 362.885C695.263 361.736 700.529 364.865 705.861 368.54C706.732 369.141 707.028 370.29 706.565 371.241C704.823 374.815 703.1 378.343 701.283 382.079C700.73 383.215 701.275 384.578 702.433 385.084C714.46 390.345 722.85 399.811 732.183 408.063C735.446 410.94 738.949 413.567 742.408 416.216C748.922 421.207 756.135 422.243 764.057 420.248C779.378 416.39 794.688 412.477 810.117 409.131C822.084 406.534 833.414 405.098 846.004 406.839C846.079 406.85 846.151 406.876 846.215 406.917ZM522.481 739.756C522.484 739.753 522.489 739.751 522.494 739.751C535.03 739.751 547.565 739.893 560.1 739.664C563.45 739.598 567.139 739.108 570.074 737.626C585.787 729.702 601.599 721.931 616.799 713.103C632.48 704.002 633.8 699.479 629.152 681.747C624.962 665.78 617.225 651.339 609.041 637.214C599.362 620.495 591.811 603.372 591.331 583.58C591.08 573.193 588.09 563.232 583.147 553.913C578.771 545.662 571.994 541.761 562.861 541.641C537.6 541.314 512.862 544.333 488.801 552.474C426.602 573.542 388.716 624.702 387.668 688.58C387.417 703.991 394.095 714.454 407.572 720.558C416.967 724.819 426.755 728.362 436.62 731.391C464.607 739.99 493.499 740.786 522.468 739.762C522.473 739.762 522.477 739.Line truncated
</mask>
<g mask="url(#mask0_5470_4284)">
<path d="M313.469 658.228C312.707 668.769 309.913 679.69 311.564 689.723C315.755 715.122 296.832 713.09 282.736 714.487C267.37 716.011 254.543 731.378 237.526 722.615C238.288 721.599 238.923 720.71 239.685 719.694C259.623 705.979 270.417 681.087 296.832 674.357C301.912 673.214 301.658 657.974 313.469 658.228Z" fill="#FEDAA5"/>
<path d="M-8.20716 410.326C-8.20716 267.947 -8.20716 125.569 -8.32803 -16.8094C-8.32803 -20.9187 -7.48198 -21.7648 -3.37258 -21.7648C281.747 -21.6439 566.987 -21.6439 852.106 -21.7648C856.216 -21.7648 857.062 -20.9187 857.062 -16.8094C856.941 268.31 856.941 553.55 857.062 838.669C857.062 842.779 856.216 843.625 852.106 843.625C566.987 843.504 281.747 843.504 -3.37258 843.625C-7.48198 843.625 -8.32803 842.779 -8.32803 838.669C-8.20716 695.808 -8.20716 553.067 -8.20716 410.326ZM-0.350972 449.607C-0.350972 573.493 -0.471837 697.379 -0.471837 821.144C1.94545 825.858 -6.87765 835.89 4.72534 835.89C258.057 836.131 511.389 836.131 764.721 836.01C766.897 836.01 771.127 832.989 770.885 832.264C766.413 812.563 778.742 813.892 791.674 814.497C805.694 815.222 819.835 814.376 833.977 814.738C839.053 814.859 840.262 813.409 840.262 808.332C840.02 751.043 840.141 693.874 840.141 636.584C840.02 421.929 840.02 207.273 839.899 -7.38192C838.932 -8.22797 838.086 -9.67835 837.119 -9.67835C811.133 -9.67835 785.268 -9.67835 759.282 -9.31575C757.953 -9.31575 755.415 -5.20636 755.415 -3.15166C755.173 69.7296 755.415 142.49 756.382 215.25C756.624 232.655 747.075 239.544 733.418 243.654C728.825 244.862 724.715 245.467 720.122 246.675C702.597 246.675 684.467 241.236 677.095 267.343C672.381 283.66 657.152 296.955 646.637 311.579C641.681 315.205 637.209 319.798 631.77 322.336C606.751 334.423 583.062 315.568 556.351 319.315C526.86 323.424 507.486 331.521 506.519 362.583C506.519 364.033 521.526 393.84 493.742 376.753C492.533 376.028 490.842 376.725 489.271 376.725C469.087 363.309 444.914 377.088 424.246 366.935C423.4 365.364 422.554 363.793 421.587 362.222C421.708 354.124 422.433 346.026 421.708 338.049C420.62 326.808 422.191 320.403 436.212 322.82C444.551 324.27 458.088 327.05 461.11 317.864C472.108 284.627 488.667 254.29 502.929 222.744C511.148 204.494 492.172 186.968 489.875 168.355C487.579 150.467 472.834 135.238 478.514 116.142C486.491 89.7931 505.467 69.9714 520.817 47.974C522.388 46.4028 523.959 44.8315 525.651 43.1394C538.826 23.5594 554.175 6.63836 577.865 -0.371785C581.491 -2.30562 589.468 1.19945 588.259 -6.41501C587.292 -12.9417 580.403 -9.55748 576.535 -9.55748C391.371 -9.43662 206.206 -9.07402 20.9212 -10.1618C-0.713563 -10.2827 -4.09777 -2.78907 -0.592699 14.7363C-0.592699 154.818 -0.592699 295.021 -0.592699 435.103C-0.471835 439.938 -0.350972 444.772 -0.350972 449.607ZM640.352 95.8363C640.11 95.8363 639.868 95.8363 639.506 95.8363C639.506 64.049 639.506 32.2616 639.506 0.474274C639.506 -3.0308 641.077 -7.50278 634.429 -7.38192C627.782 -7.26105 629.716 -2.54734 629.716 0.715998C629.232 62.7195 628.87 124.723 628.386 186.726C628.386 193.616 631.287 198.088 637.572 200.505C638.055 200.747 640.231 198.692 640.231 197.846C640.352 163.762 640.352 129.799 640.352 95.8363Z" fill="#FCE8EC"/>
<path d="M839.683 -7.14046C839.803 207.515 839.803 422.17 839.924 636.825C839.924 694.115 839.924 751.284 840.045 808.574C840.045 813.529 838.836 815.101 833.76 814.98C819.619 814.617 805.599 815.342 791.458 814.738C778.404 814.134 766.076 812.804 770.669 832.505C770.911 833.351 766.68 836.252 764.505 836.252C511.173 836.373 257.841 836.373 4.50893 836.131C-7.09406 836.131 1.60818 826.099 -0.688248 821.386C0.641262 820.056 1.97077 818.727 3.17942 817.397C16.3536 802.772 29.5279 788.148 42.5812 773.523C89.3558 772.798 136.251 772.556 183.026 770.381C188.465 770.139 189.673 787.664 196.321 772.919C198.617 767.843 202.848 763.733 206.111 759.14C205.386 762.524 202.968 766.876 204.177 769.172C207.803 775.82 210.583 768.326 212.396 766.996C224.12 757.569 234.151 768.205 243.941 770.622C250.347 772.194 254.578 783.313 259.412 790.323C263.642 796.487 267.268 803.135 271.136 809.541C271.378 804.223 271.861 798.905 271.982 793.708C272.103 788.873 271.499 784.038 272.103 779.204C274.037 763.975 270.773 749.713 290.233 738.231C305.824 729.045 329.151 716.959 324.921 689.039C323.108 677.194 324.921 664.866 325.162 652.659C337.249 643.956 349.456 635.133 361.543 626.431C378.584 611.806 398.285 602.379 419.558 595.852C420.162 595.852 420.766 595.852 421.371 595.852C422.337 595.49 423.425 595.006 424.392 594.644C448.323 597.061 445.543 577.36 449.774 563.702C480.594 551.979 515.645 574.58 545.257 551.616C553.475 545.21 564.474 541.826 571.122 534.453C582.241 522.125 593.844 512.818 610.886 509.918C620.313 508.346 635.542 514.511 638.201 502.424C640.981 489.854 636.026 475.713 634.455 462.176C640.256 454.924 645.937 447.793 651.738 440.542C656.573 425.554 666.363 437.036 673.615 436.795C697.908 435.707 701.776 413.468 713.137 399.448C734.047 373.583 746.617 342.762 756.89 311.821C766.68 282.693 757.495 251.872 757.495 221.656C757.616 147.566 757.495 73.597 757.615 -0.492919C757.615 -2.42675 758.582 -4.48145 759.066 -6.41528C760.516 -5.69009 761.967 -5.08577 763.296 -4.36058C762.329 -3.99798 761.241 -3.6354 760.275 -3.27281C760.637 -2.91021 760.879 -2.54762 761.241 -2.06416C761.846 -2.78935 762.571 -3.51453 763.175 -4.36058C788.919 -5.56922 814.301 -6.41528 839.683 -7.14046Z" fill="#F18928" stroke-width="1.34889" stroke="#F18928"/>
<path d="M-0.807763 435.101C-0.807763 295.019 -0.807763 154.816 -0.807763 14.7345C-0.203441 14.7345 0.521746 14.6137 1.12607 14.6137C74.6117 14.6137 148.097 15.3388 221.583 14.1302C241.646 13.7676 255.546 19.3274 268.962 33.952C281.773 47.7305 297.969 58.3666 312.715 70.3322C325.768 81.8143 319.725 98.7353 319.362 110.943C318.395 146.114 318.395 181.89 307.034 216.216C304.254 224.797 304.617 234.587 301.716 243.168C295.673 261.298 306.551 281.482 292.651 299.129C281.532 313.27 273.675 330.07 264.369 345.661C256.15 358.836 244.184 369.955 242.613 386.634C241.767 386.876 240.921 386.997 240.075 387.239C222.187 383.734 200.794 403.193 186.17 378.657C186.17 378.657 185.203 379.02 185.082 379.262C180.731 392.919 166.469 393.403 157.162 395.82C116.068 406.577 73.5239 406.094 31.5839 403.072C4.51027 401.138 -4.0711 409.599 0.884341 434.98C0.400883 435.101 -0.203441 435.101 -0.807763 435.101Z" fill="#80C9C1" stroke-width="1.34889" stroke="#80C9C1"/>
<path d="M264.488 345.543C273.794 329.951 281.65 313.151 292.77 299.01C306.669 281.364 295.792 261.179 301.835 243.05C304.736 234.468 304.252 224.799 307.153 216.097C318.393 181.892 318.393 145.996 319.481 110.824C319.844 98.4958 325.766 81.6956 312.833 70.2135C330.48 78.3114 354.169 68.2796 367.102 92.5734C377.979 112.999 393.934 87.3762 407.833 92.2108C410.371 83.992 417.381 78.3114 417.623 68.0379C417.865 59.94 420.161 42.1729 429.951 44.7111C460.288 52.6881 490.625 42.8981 520.479 48.337C505.129 70.3343 486.032 90.1561 478.176 116.505C472.495 135.48 487.241 150.83 489.537 168.718C491.834 187.331 510.809 204.856 502.591 223.107C488.329 254.653 471.77 285.111 460.772 318.227C457.75 327.413 444.213 324.633 435.874 323.183C421.732 320.766 420.282 327.05 421.37 338.412C422.095 346.389 421.37 354.487 421.249 362.585C404.207 361.739 387.165 361.376 370.244 359.805C365.168 359.321 360.333 355.937 355.378 353.882C357.795 349.048 355.499 341.433 363.717 339.862C364.563 339.741 366.014 336.72 365.651 336.115C360.938 329.589 355.982 323.304 350.906 317.14C350.785 317.019 347.884 318.59 346.796 319.799C324.799 345.18 299.659 361.497 264.488 345.543Z" fill="#95C7DB" stroke-width="1.34889" stroke="#95C7DB"/>
<path d="M520.601 48.3367C490.747 43.0186 460.41 52.6878 430.073 44.7107C420.162 42.1726 417.987 59.9397 417.745 68.0376C417.382 78.3111 410.493 83.9917 407.955 92.2105C394.056 87.3759 378.101 112.999 367.224 92.5731C354.291 68.2793 330.602 78.3111 312.955 70.2132C298.21 58.2476 281.893 47.6115 269.203 33.8329C255.787 19.2083 241.766 13.6486 221.824 14.0112C148.096 15.3407 74.6107 14.6155 1.12514 14.6155C4.9928 7.84708 -7.33537 -6.77753 8.25614 -6.8984C190.641 -7.50272 373.146 -7.38185 555.53 -6.8984C562.903 -6.8984 570.155 -2.4264 577.528 -0.0091138C553.838 6.88016 538.489 23.9221 525.314 43.5021C523.743 45.0733 522.172 46.6446 520.601 48.3367Z" fill="#87ADD4" stroke-width="1.34889" stroke="#87ADD4"/>
<path d="M498.602 502.547C499.81 490.098 505.854 483.934 519.028 482.967C532.806 482 524.104 468.947 527.73 461.937C536.07 445.62 541.75 427.611 540.904 408.515C546.222 406.581 551.903 405.372 556.617 402.471C573.054 392.319 584.778 376.969 605.929 371.168C617.532 367.904 629.377 368.75 640.255 365.003C660.197 358.114 659.231 336.479 668.054 321.613C678.931 312.911 683.887 300.945 690.172 288.859C695.973 277.86 687.15 256.83 709.268 255.742C715.795 255.379 718.212 252.116 719.3 246.556C723.893 245.348 728.585 244.232 733.178 243.023C744.056 296.445 721.476 339.984 682.316 369.838C655.242 390.385 656.088 414.437 651.737 440.302C645.935 447.554 640.255 454.685 634.453 461.937C628.289 473.661 617.291 467.738 608.347 468.826C600.37 469.793 592.392 467.496 585.02 473.661C560.243 494.57 531.477 505.085 498.602 502.547Z" fill="#FEDAA5" stroke-width="1.34889" stroke="#FEDAA5"/>
<path d="M668.424 321.413C659.601 336.279 660.396 357.987 640.454 364.998C629.576 368.744 617.68 367.868 606.077 371.131C584.926 376.933 573.171 392.323 556.733 402.475C552.02 405.376 546.124 406.608 540.926 408.542C523.643 398.027 506.368 387.192 488.963 376.676C490.534 376.676 492.589 375.951 493.677 376.676C520.509 393.235 506.126 363.986 506.126 362.535C507.093 331.473 526.673 323.375 556.043 319.266C582.754 315.519 606.322 334.374 631.462 322.287C636.901 319.628 641.373 315.156 646.329 311.53C653.701 314.794 661.172 318.149 668.424 321.413Z" fill="#FFF1CF" stroke-width="1.34889" stroke-linejoin="bevel" stroke="#FFF1CF"/>
<path d="M449.73 563.404C445.372 577.206 448.278 596.82 424.305 594.399C423.787 592.976 421.607 594.497 421.278 595.731C420.672 595.731 419.762 595.3 419.156 595.3C415.524 526.166 418.493 456.979 407.112 388.329C408.081 377.554 414.861 371.258 424.063 366.778C444.766 377.069 468.981 363.267 489.201 376.585C506.515 387.119 523.481 397.969 540.916 408.503C541.763 427.633 536.42 445.235 528.066 461.701C524.434 468.723 533.151 481.799 519.349 482.768C506.151 483.615 500.098 489.911 498.887 502.382C482.3 522.722 465.954 543.063 449.73 563.404Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
<path d="M-0.807763 435.103C-0.203441 435.103 0.400882 434.982 1.0052 434.982C75.216 437.642 149.427 441.147 223.517 432.686C202.003 460.122 180.731 482.966 139.032 479.34C93.95 475.472 48.0215 480.79 2.57644 482.241C1.48866 471.363 0.521747 460.485 -0.566033 449.607C-0.686898 444.773 -0.807763 439.938 -0.807763 435.103Z" fill="#797AAA" stroke-width="1.34889" stroke="#797AAA"/>
<path d="M651.736 440.3C656.088 414.435 655.242 390.383 682.315 369.836C721.475 339.982 744.077 296.471 733.078 243.049C746.736 238.94 755.68 233.017 755.438 215.613C754.471 142.853 755.076 69.9712 755.196 -2.7892C755.196 -4.96476 757.614 -8.95329 759.064 -8.95329C785.05 -9.43674 810.915 -9.43674 836.901 -9.31588C837.868 -9.31588 838.714 -7.86551 839.681 -7.01946C814.299 -6.29427 788.918 -5.44822 763.536 -4.72303C762.086 -5.44822 760.635 -6.05253 759.306 -6.77772C758.822 -4.84389 757.855 -2.7892 757.855 -0.855369C757.735 73.2346 757.855 147.204 757.735 221.294C757.614 251.389 766.92 282.33 757.13 311.458C746.736 342.4 734.287 373.22 713.377 399.085C702.016 413.105 698.148 435.465 673.855 436.432C666.361 436.795 656.571 425.313 651.736 440.3Z" fill="#F8B570"/>
<path d="M424.42 366.647C415.355 371.118 408.194 377.935 407.106 388.692C402.271 391.955 397.316 395.339 392.481 398.603C368.309 397.877 344.257 397.031 320.084 396.306C315.249 395.581 310.414 394.735 305.58 394.01C285.637 382.528 263.761 388.208 242.73 386.516C244.302 369.837 256.267 358.717 264.486 345.543C299.658 361.376 324.676 345.06 346.916 319.678C348.003 318.469 350.904 316.898 351.025 317.019C356.101 323.183 361.057 329.468 365.77 335.995C366.133 336.599 364.803 339.5 363.837 339.742C355.618 341.313 358.035 348.927 355.497 353.762C360.452 355.816 365.287 359.201 370.363 359.684C387.284 361.135 404.806 361.362 421.848 362.208C422.694 363.78 423.574 365.075 424.42 366.647Z" fill="#87ADD4"/>
<path d="M640.578 95.6923C640.578 129.796 640.398 164.837 640.276 198.941C640.276 199.912 638.092 201.854 637.606 201.611C631.416 199.184 628.382 194.814 628.382 187.775C628.868 125.514 629.072 63.1936 629.558 0.932391C629.558 -2.34452 627.727 -7.33012 634.402 -7.45149C640.956 -7.57285 639.703 -3.06418 639.824 0.455463C639.824 32.375 639.728 63.6514 639.728 95.5709C639.971 95.6923 640.214 95.6923 640.578 95.6923Z" fill="#FCE8EC"/>
<path d="M668.056 321.732C660.804 318.468 653.311 314.601 646.059 311.337C656.574 296.713 671.561 283.539 676.275 267.222C683.769 241.236 702.922 246.407 720.327 246.286C719.239 251.846 716.039 255.498 709.513 255.74C687.395 256.828 696.58 277.979 690.779 288.977C684.615 301.185 678.934 313.03 668.056 321.732Z" fill="#F8C9D1" stroke-width="1.34889" stroke="#F8C9D1"/>
<path d="M577.525 -0.0101719C570.153 -2.42746 562.901 -6.89945 555.528 -6.89945C373.144 -7.26205 190.638 -7.50377 8.25367 -6.89945C-7.33785 -6.89945 4.86947 7.72515 1.12267 14.6144C0.518345 14.6144 -0.206842 14.7353 -0.811164 14.7353C-4.4371 -2.79006 -1.05289 -10.2837 20.5818 -10.1628C205.746 -9.07501 391.031 -9.55847 576.196 -9.31674C580.184 -9.31674 586.953 -12.7009 587.92 -6.17426C589.249 1.56107 581.272 -1.944 577.525 -0.0101719Z" fill="#95C7DB" stroke-width="1.34889" stroke="#95C7DB"/>
<path d="M-0.686898 449.607C0.400883 460.485 1.3678 471.363 2.45558 482.24C1.97212 489.613 1.0052 496.986 1.0052 504.359C0.88434 606.368 0.88434 708.378 1.0052 810.388C1.0052 812.684 2.33472 814.98 3.0599 817.277C1.73039 818.606 0.400882 819.936 -0.807763 821.265C-0.807763 697.379 -0.686898 573.493 -0.686898 449.607Z" fill="#ADA0CC"/>
<path d="M520.6 48.339C522.171 46.7677 523.743 45.1965 525.435 43.5044C523.743 45.0756 522.171 46.6469 520.6 48.339Z" fill="#95C7DB"/>
<path d="M3.05605 817.275C2.33087 814.979 1.00135 812.682 1.00135 810.386C0.88049 708.376 0.88049 606.367 1.00135 504.357C1.00135 496.984 1.96827 489.612 2.45173 482.239C48.0176 480.909 93.8253 475.471 138.908 479.338C180.606 482.964 201.878 460.121 223.392 432.684C223.996 432.564 224.722 432.443 225.326 432.201C233.786 429.059 237.775 433.893 245.148 438.244C273.793 454.924 304.976 446.342 332.412 433.047C350.783 424.224 363.353 429.421 369.759 443.079C386.438 479.217 379.186 518.982 383.537 557.054C388.493 599.84 362.628 589.567 340.51 589.687C325.764 589.808 311.14 589.929 296.394 589.567C293.252 589.446 289.626 588.116 287.209 586.061C270.892 573.008 251.674 588.841 234.995 580.623C217.107 571.799 205.141 594.039 188.704 589.204C183.628 587.754 179.881 577.964 175.53 578.084C149.423 578.568 123.074 578.689 100.956 596.818C91.408 604.675 75.333 606.367 74.6078 622.079C73.3992 646.373 73.6409 670.787 73.7618 695.081C73.8826 717.441 74.1244 717.441 86.936 722.034C87.6612 722.276 87.842 724.11 88.2046 725.198C72.9757 741.273 57.8077 757.326 42.5787 773.281C29.4045 788.026 16.2303 802.651 3.05605 817.275Z" fill="#6E5EA6" stroke-width="1.34889" stroke="#6E5EA6"/>
<path d="M88.1444 725.295C87.7818 724.208 87.6609 722.395 86.9357 722.153C74.1241 717.56 73.8824 717.681 73.7615 695.2C73.6406 670.906 73.5198 646.492 74.6076 622.198C75.3327 606.486 91.4077 604.793 100.956 596.937C123.074 578.808 149.423 578.566 175.529 578.203C179.76 578.082 183.627 587.872 188.704 589.323C205.141 594.157 217.107 571.918 234.995 580.741C251.553 588.96 270.891 573.127 287.208 586.18C289.625 588.114 293.251 589.565 296.394 589.685C311.139 590.048 325.764 589.927 340.509 589.806C362.628 589.685 388.372 599.959 383.537 557.173C379.186 519.101 386.438 479.336 369.759 443.198C363.474 429.54 350.783 424.343 332.411 433.166C304.975 446.461 273.792 455.042 245.147 438.363C237.775 434.012 233.786 429.177 225.326 432.32C230.281 417.333 235.222 402.117 240.177 387.13C241.023 386.888 241.895 386.753 242.741 386.511C263.771 388.203 285.629 382.493 305.571 393.976C310.406 394.701 315.274 395.559 320.108 396.284C323.371 401.844 386.47 403.709 392.414 398.626C397.249 395.363 402.287 391.866 407.121 388.603C418.482 457.133 415.687 526.836 419.313 595.85C398.041 602.255 378.219 611.683 361.298 626.428C340.993 625.582 320.204 626.791 300.382 623.286C272.1 618.33 250.345 630.538 228.468 644.437C219.403 650.118 209.492 651.568 199.944 648.788C167.19 639.482 137.336 659.424 139.028 692.541C140.237 714.78 137.336 727.954 110.746 720.461C105.307 718.89 98.6596 721.428 92.6164 722.032C91.166 722.999 89.5948 724.087 88.1444 725.295Z" fill="#797AAA" stroke-width="1.34889" stroke="#797AAA"/>
<path d="M92.7403 721.915C98.7835 721.311 105.431 718.773 110.87 720.344C137.581 727.838 140.361 714.784 139.152 692.424C137.46 659.307 167.193 639.365 200.068 648.671C209.616 651.451 219.527 650.001 228.592 644.32C250.468 630.542 272.224 618.213 300.506 623.169C320.328 626.674 341.117 625.465 361.422 626.311C349.336 635.014 337.128 643.837 325.042 652.539C320.812 655.077 316.702 657.615 312.472 660.033C301.352 659.791 301.473 674.295 296.518 675.503C271.378 681.909 261.105 705.599 242.129 718.652C240.195 715.147 238.865 714.18 234.273 716.718C227.142 720.586 218.923 724.091 210.946 724.453C174.445 726.025 138.064 726.508 101.563 726.871C98.5418 726.992 95.641 723.607 92.7403 721.915Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
<path d="M498.602 502.543C531.477 505.202 560.243 494.566 585.141 473.656C592.514 467.492 600.491 469.789 608.468 468.822C617.412 467.734 628.41 473.656 634.575 461.933C636.146 475.469 640.98 489.611 638.321 502.18C635.662 514.388 620.433 508.103 611.006 509.674C593.964 512.575 582.361 521.761 571.242 534.21C564.473 541.703 553.475 545.087 545.377 551.372C515.765 574.457 480.714 551.735 449.894 563.459C465.969 543.154 482.285 522.848 498.602 502.543Z" fill="#F8B570"/>
<path d="M92.7394 721.915C95.7085 723.67 98.6479 726.973 101.549 726.852C138.05 726.369 174.551 725.895 210.931 724.444C218.908 724.082 227.104 720.615 234.235 716.626C238.828 714.088 240.339 715.075 242.273 718.58C241.548 719.547 240.798 720.585 240.073 721.552C228.712 734.001 217.472 746.571 206.11 759.02C202.726 763.613 198.617 767.722 196.32 772.799C189.673 787.544 188.585 770.019 183.025 770.261C136.251 772.557 89.2649 772.636 42.4903 773.362C57.7193 757.287 72.8736 741.239 88.1025 725.285C89.5529 724.076 91.1681 723.003 92.7394 721.915Z" fill="#B59259" stroke-width="1.34889" stroke="#B59259"/>
<path d="M206.107 758.899C217.469 746.45 228.709 733.88 240.07 721.431C256.266 729.771 268.473 715.146 283.098 713.696C296.514 712.366 314.523 714.3 310.534 690.127C308.963 680.458 311.622 670.185 312.347 660.153C316.577 657.615 320.687 655.077 324.917 652.659C324.796 664.746 322.862 677.195 324.675 689.04C328.906 716.959 305.579 729.046 289.987 738.231C270.528 749.714 273.791 763.975 271.858 779.204C271.253 783.918 271.858 788.874 271.737 793.708C271.616 799.026 271.132 804.344 270.891 809.541C267.023 803.136 263.397 796.488 259.167 790.324C254.332 783.193 250.102 772.194 243.696 770.623C233.906 768.206 223.874 757.691 212.15 766.997C210.458 768.327 207.679 775.82 203.932 769.173C202.965 766.755 205.382 762.404 206.107 758.899Z" fill="#F8B570"/>
<path d="M240.193 387.12C235.238 402.107 230.403 417.215 225.448 432.202C224.843 432.323 224.118 432.444 223.514 432.686C149.424 441.267 75.2133 437.641 1.00251 434.982C-3.95294 409.721 4.74931 401.261 31.7021 403.074C73.642 405.975 116.186 406.458 157.28 395.822C166.587 393.405 180.849 392.8 185.2 379.264C185.321 378.901 186.288 378.659 186.288 378.659C200.912 403.074 222.426 383.736 240.193 387.12Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
<path d="M392.483 398.6C387.285 403.918 323.469 401.863 320.085 396.304C344.258 397.029 368.431 397.875 392.483 398.6Z" fill="#7D89C1" stroke-width="1.34889" stroke="#7D89C1"/>
<path d="M320.079 396.306C315.245 395.581 310.41 394.735 305.575 394.01C310.41 394.735 315.245 395.581 320.079 396.306Z" fill="#7D89C1"/>
<g mask="url(#mask0_5470_4251)">
<g id="Group 96">
<path id="Vector_2" d="M427.284 700.798C426.676 709.205 424.448 717.916 425.765 725.918C429.107 746.177 414.015 744.557 402.771 745.671C390.514 746.886 380.284 759.143 366.71 752.154C367.318 751.343 367.825 750.634 368.432 749.824C384.335 738.884 392.945 719.03 414.015 713.662C418.066 712.75 417.864 700.595 427.284 700.798Z" fill="#FEDAA5"/>
<path id="Vector_3" d="M170.709 503.066C170.709 389.503 170.709 275.939 170.613 162.375C170.613 159.098 171.288 158.423 174.566 158.423C401.982 158.519 629.495 158.519 856.911 158.423C860.189 158.423 860.864 159.098 860.864 162.375C860.767 389.792 860.767 617.305 860.864 844.721C860.864 847.999 860.189 848.674 856.911 848.674C629.495 848.577 401.982 848.577 174.566 848.674C171.288 848.674 170.613 847.999 170.613 844.721C170.709 730.772 170.709 616.919 170.709 503.066ZM176.976 534.397C176.976 633.211 176.879 732.025 176.879 830.742C178.807 834.502 171.77 842.504 181.025 842.504C383.087 842.697 585.149 842.697 787.211 842.6C788.947 842.6 792.321 840.19 792.128 839.612C788.561 823.898 798.394 824.958 808.709 825.44C819.892 826.019 831.171 825.344 842.451 825.633C846.499 825.729 847.464 824.573 847.464 820.524C847.271 774.828 847.367 729.229 847.367 683.534C847.271 512.321 847.271 341.108 847.174 169.895C846.403 169.22 845.728 168.063 844.957 168.063C824.23 168.063 803.6 168.063 782.873 168.353C781.813 168.353 779.788 171.63 779.788 173.269C779.595 231.401 779.788 289.436 780.56 347.471C780.752 361.353 773.136 366.848 762.243 370.125C758.58 371.09 755.302 371.572 751.638 372.536C737.66 372.536 723.199 368.197 717.319 389.021C713.559 402.035 701.412 412.64 693.025 424.304C689.072 427.197 685.505 430.86 681.167 432.884C661.212 442.525 642.316 427.486 621.011 430.474C597.489 433.752 582.035 440.21 581.264 464.986C581.264 466.143 593.235 489.917 571.073 476.288C570.109 475.71 568.76 476.266 567.507 476.266C551.408 465.565 532.127 476.555 515.642 468.457C514.967 467.204 514.292 465.951 513.521 464.698C513.617 458.239 514.196 451.779 513.617 445.417C512.75 436.451 514.003 431.342 525.186 433.27C531.838 434.427 542.635 436.644 545.045 429.317C553.818 402.806 567.025 378.609 578.401 353.448C584.956 338.891 569.821 324.912 567.989 310.066C566.157 295.798 554.396 283.651 558.927 268.42C565.29 247.404 580.425 231.593 592.669 214.048C593.922 212.795 595.175 211.541 596.525 210.192C607.033 194.574 619.276 181.078 638.171 175.486C641.063 173.944 647.426 176.74 646.462 170.666C645.691 165.46 640.196 168.16 637.111 168.16C489.42 168.256 341.73 168.545 193.943 167.678C176.686 167.581 173.987 173.558 176.783 187.537C176.783 299.269 176.783 411.097 176.783 522.829C176.879 526.685 176.976 530.541 176.976 534.397ZM688.012 252.224C687.819 252.224 687.626 252.224 687.337 252.224C687.337 226.87 687.337 201.515 687.337 176.161C687.337 173.366 688.59 169.799 683.288 169.895C677.986 169.991 679.528 173.751 679.528 176.354C679.143 225.809 678.853 275.264 678.468 324.719C678.468 330.214 680.782 333.781 685.795 335.709C686.18 335.902 687.915 334.263 687.915 333.589C688.012 306.403 688.012 279.313 688.012 252.224Z" fill="#FCE8EC"/>
<path id="Vector_4" d="M847.002 170.088C847.098 341.301 847.098 512.514 847.194 683.726C847.194 729.422 847.194 775.021 847.291 820.716C847.291 824.669 846.327 825.922 842.278 825.826C830.999 825.536 819.816 826.115 808.537 825.633C798.125 825.151 788.292 824.09 791.955 839.804C792.148 840.479 788.774 842.793 787.038 842.793C584.976 842.889 382.914 842.889 180.852 842.696C171.597 842.696 178.538 834.695 176.707 830.935C177.767 829.874 178.828 828.814 179.792 827.754C190.3 816.089 200.808 804.424 211.219 792.759C248.527 792.181 285.932 791.988 323.24 790.253C327.578 790.06 328.542 804.038 333.845 792.277C335.676 788.228 339.05 784.95 341.653 781.287C341.075 783.986 339.147 787.457 340.111 789.288C343.003 794.591 345.22 788.614 346.666 787.553C356.018 780.034 364.019 788.517 371.828 790.445C376.937 791.699 380.311 800.568 384.167 806.159C387.542 811.076 390.434 816.378 393.519 821.487C393.711 817.246 394.097 813.004 394.193 808.858C394.29 805.002 393.808 801.146 394.29 797.29C395.832 785.143 393.229 773.768 408.75 764.609C421.186 757.283 439.792 747.642 436.418 725.373C434.972 715.925 436.418 706.092 436.611 696.355C446.251 689.414 455.988 682.377 465.629 675.436C479.221 663.771 494.935 656.251 511.902 651.046C512.384 651.046 512.866 651.046 513.348 651.046C514.12 650.756 514.987 650.371 515.758 650.082C534.846 652.01 532.629 636.296 536.003 625.402C560.586 616.051 588.543 634.079 612.162 615.762C618.718 610.652 627.49 607.953 632.793 602.073C641.662 592.239 650.916 584.816 664.509 582.503C672.029 581.249 684.176 586.166 686.297 576.526C688.514 566.5 684.561 555.22 683.308 544.423C687.936 538.639 692.466 532.951 697.094 527.167C700.95 515.213 708.759 524.371 714.543 524.178C733.92 523.311 737.005 505.572 746.067 494.39C762.745 473.759 772.771 449.176 780.965 424.497C788.774 401.264 781.447 376.681 781.447 352.58C781.543 293.484 781.447 234.485 781.543 175.39C781.543 173.847 782.315 172.208 782.7 170.666C783.857 171.244 785.014 171.726 786.074 172.305C785.303 172.594 784.436 172.883 783.664 173.172C783.954 173.462 784.146 173.751 784.436 174.137C784.918 173.558 785.496 172.98 785.978 172.305C806.512 171.341 826.757 170.666 847.002 170.088Z" fill="#F18928" stroke-width="1.0759" stroke="#F18928"/>
<path id="Vector_5" d="M176.611 522.827C176.611 411.096 176.611 299.267 176.611 187.535C177.093 187.535 177.672 187.439 178.154 187.439C236.767 187.439 295.381 188.017 353.994 187.053C369.997 186.764 381.084 191.199 391.784 202.864C402.003 213.854 414.921 222.337 426.683 231.881C437.094 241.04 432.274 254.536 431.985 264.273C431.213 292.326 431.213 320.862 422.152 348.24C419.934 355.085 420.223 362.894 417.91 369.738C413.09 384.199 421.766 400.298 410.679 414.373C401.81 425.653 395.544 439.053 388.121 451.489C381.566 461.997 372.022 470.866 370.768 484.17C370.094 484.362 369.419 484.459 368.744 484.652C354.476 481.856 337.413 497.377 325.748 477.807C325.748 477.807 324.977 478.096 324.88 478.289C321.41 489.183 310.034 489.568 302.611 491.496C269.834 500.076 235.9 499.691 202.448 497.28C180.853 495.738 174.008 502.486 177.961 522.731C177.575 522.827 177.093 522.827 176.611 522.827Z" fill="#80C9C1" stroke-width="1.0759" stroke="#80C9C1"/>
<path id="Vector_6" d="M388.216 451.394C395.639 438.958 401.905 425.558 410.774 414.279C421.861 400.204 413.184 384.104 418.005 369.644C420.318 362.799 419.933 355.087 422.246 348.146C431.212 320.864 431.212 292.232 432.079 264.178C432.369 254.345 437.092 240.945 426.777 231.786C440.852 238.246 459.747 230.244 470.063 249.621C478.739 265.913 491.464 245.476 502.551 249.332C504.575 242.777 510.166 238.246 510.359 230.051C510.552 223.592 512.384 209.421 520.192 211.445C544.39 217.808 568.587 209.999 592.399 214.337C580.156 231.883 564.924 247.693 558.658 268.709C554.127 283.844 565.888 296.088 567.719 310.356C569.551 325.202 584.687 339.18 578.131 353.737C566.755 378.899 553.548 403.192 544.775 429.607C542.365 436.934 531.568 434.716 524.916 433.559C513.637 431.631 512.48 436.644 513.348 445.706C513.926 452.069 513.348 458.528 513.251 464.987C499.658 464.312 486.066 464.023 472.569 462.77C468.52 462.384 464.664 459.685 460.711 458.046C462.639 454.19 460.808 448.116 467.363 446.863C468.038 446.767 469.195 444.357 468.906 443.875C465.146 438.669 461.193 433.656 457.144 428.739C457.048 428.643 454.734 429.896 453.867 430.86C436.321 451.105 416.269 464.119 388.216 451.394Z" fill="#95C7DB" stroke-width="1.0759" stroke="#95C7DB"/>
<path id="Vector_7" d="M592.496 214.337C568.684 210.095 544.487 217.808 520.29 211.445C512.385 209.421 510.649 223.592 510.457 230.051C510.167 238.245 504.672 242.776 502.648 249.332C491.561 245.476 478.836 265.913 470.16 249.621C459.845 230.244 440.95 238.245 426.875 231.786C415.113 222.242 402.099 213.759 391.976 202.769C381.276 191.104 370.093 186.669 354.186 186.959C295.38 188.019 236.766 187.441 178.153 187.441C181.238 182.042 171.405 170.377 183.841 170.281C329.314 169.799 474.884 169.895 620.357 170.281C626.237 170.281 632.022 173.848 637.902 175.776C619.007 181.271 606.764 194.864 596.256 210.481C595.003 211.734 593.749 212.988 592.496 214.337Z" fill="#87ADD4" stroke-width="1.0759" stroke="#87ADD4"/>
<path id="Vector_8" d="M574.949 576.624C575.913 566.694 580.734 561.778 591.242 561.006C602.232 560.235 595.291 549.824 598.183 544.232C604.834 531.218 609.365 516.853 608.691 501.622C612.932 500.079 617.463 499.115 621.223 496.801C634.334 488.704 643.685 476.46 660.556 471.833C669.811 469.23 679.258 469.905 687.934 466.916C703.841 461.421 703.07 444.165 710.107 432.307C718.784 425.366 722.736 415.822 727.749 406.182C732.377 397.409 725.339 380.635 742.981 379.767C748.187 379.478 750.115 376.875 750.982 372.441C754.646 371.477 758.388 370.586 762.052 369.622C770.728 412.233 752.718 446.961 721.483 470.772C699.889 487.161 700.563 506.345 697.093 526.976C692.465 532.76 687.934 538.448 683.307 544.232C678.391 553.583 669.618 548.859 662.484 549.727C656.121 550.498 649.759 548.667 643.878 553.583C624.115 570.261 601.171 578.648 574.949 576.624Z" fill="#FEDAA5" stroke-width="1.0759" stroke="#FEDAA5"/>
<path id="Vector_9" d="M710.402 432.148C703.365 444.005 704 461.32 688.093 466.912C679.417 469.9 669.929 469.201 660.674 471.804C643.803 476.431 634.427 488.707 621.316 496.805C617.556 499.118 612.854 500.101 608.708 501.644C594.923 493.257 581.144 484.614 567.261 476.227C568.515 476.227 570.154 475.648 571.021 476.227C592.423 489.434 580.951 466.104 580.951 464.948C581.722 440.172 597.339 433.713 620.765 430.435C642.071 427.447 660.869 442.486 680.921 432.845C685.26 430.724 688.826 427.157 692.779 424.265C698.66 426.868 704.618 429.545 710.402 432.148Z" fill="#FFF1CF" stroke-width="1.0759" stroke-linejoin="bevel" stroke="#FFF1CF"/>
<path id="Vector_10" d="M535.969 625.164C532.492 636.173 534.81 651.818 515.689 649.886C515.276 648.751 513.537 649.965 513.274 650.949C512.792 650.949 512.065 650.605 511.582 650.605C508.685 595.463 511.053 540.278 501.976 485.522C502.748 476.927 508.156 471.905 515.496 468.332C532.009 476.54 551.324 465.531 567.451 476.154C581.261 484.556 594.794 493.21 608.7 501.612C609.376 516.87 605.114 530.91 598.451 544.044C595.553 549.645 602.507 560.075 591.497 560.847C580.971 561.523 576.143 566.545 575.177 576.492C561.947 592.716 548.909 608.94 535.969 625.164Z" fill="#A29EA1" stroke-width="1.0759" stroke="#A29EA1"/>
<path id="Vector_11" d="M176.611 522.829C177.093 522.829 177.575 522.733 178.057 522.733C237.249 524.854 296.441 527.65 355.537 520.901C338.377 542.785 321.41 561.005 288.15 558.113C252.192 555.028 215.558 559.27 179.311 560.427C178.443 551.75 177.672 543.074 176.804 534.398C176.708 530.542 176.611 526.685 176.611 522.829Z" fill="#797AAA" stroke-width="1.0759" stroke="#797AAA"/>
<path id="Vector_12" d="M697.092 526.974C700.563 506.344 699.888 487.16 721.483 470.771C752.717 446.959 770.745 412.254 761.972 369.643C772.866 366.366 780 361.642 779.807 347.76C779.036 289.725 779.518 231.593 779.614 173.558C779.614 171.823 781.542 168.642 782.699 168.642C803.426 168.256 824.056 168.256 844.783 168.352C845.554 168.352 846.229 169.509 847 170.184C826.755 170.763 806.511 171.437 786.266 172.016C785.109 171.437 783.952 170.955 782.892 170.377C782.506 171.919 781.735 173.558 781.735 175.101C781.638 234.196 781.735 293.195 781.638 352.291C781.542 376.295 788.965 400.975 781.156 424.208C772.866 448.887 762.936 473.47 746.258 494.101C737.196 505.283 734.111 523.118 714.734 523.889C708.757 524.179 700.949 515.02 697.092 526.974Z" fill="#F8B570"/>
<path id="Vector_13" d="M515.781 468.227C508.55 471.794 502.838 477.231 501.971 485.81C498.115 488.413 494.162 491.113 490.306 493.716C471.025 493.137 451.841 492.462 432.56 491.884C428.704 491.305 424.848 490.631 420.992 490.052C405.085 480.894 387.636 485.425 370.862 484.075C372.115 470.771 381.659 461.902 388.214 451.394C416.268 464.023 436.223 451.009 453.962 430.764C454.829 429.8 457.143 428.547 457.239 428.643C461.288 433.56 465.241 438.573 469.001 443.778C469.29 444.26 468.229 446.574 467.458 446.767C460.903 448.02 462.831 454.094 460.806 457.95C464.759 459.589 468.615 462.288 472.664 462.674C486.161 463.83 500.136 464.012 513.729 464.687C514.404 465.94 515.106 466.974 515.781 468.227Z" fill="#87ADD4"/>
<path id="Vector_14" d="M688.192 252.109C688.192 279.311 688.048 307.26 687.951 334.462C687.951 335.236 686.209 336.785 685.822 336.592C680.885 334.655 678.465 331.17 678.465 325.556C678.852 275.895 679.015 226.187 679.402 176.527C679.402 173.913 677.942 169.936 683.266 169.839C688.494 169.743 687.494 173.339 687.591 176.146C687.591 201.606 687.514 226.552 687.514 252.012C687.708 252.109 687.902 252.109 688.192 252.109Z" fill="#FCE8EC"/>
<path id="Vector_15" d="M710.109 432.402C704.325 429.799 698.348 426.714 692.564 424.111C700.951 412.446 712.905 401.938 716.665 388.924C722.642 368.197 737.919 372.322 751.801 372.225C750.934 376.66 748.382 379.573 743.176 379.766C725.534 380.633 732.861 397.504 728.233 406.277C723.317 416.013 718.785 425.461 710.109 432.402Z" fill="#F8C9D1" stroke-width="1.0759" stroke="#F8C9D1"/>
<path id="Vector_16" d="M637.9 175.775C632.02 173.847 626.235 170.28 620.355 170.28C474.882 169.991 329.312 169.798 183.839 170.28C171.403 170.28 181.14 181.945 178.151 187.44C177.669 187.44 177.091 187.536 176.609 187.536C173.716 173.558 176.416 167.581 193.672 167.677C341.363 168.545 489.149 168.159 636.84 168.352C640.021 168.352 645.42 165.652 646.191 170.858C647.251 177.028 640.889 174.232 637.9 175.775Z" fill="#95C7DB" stroke-width="1.0759" stroke="#95C7DB"/>
<path id="Vector_17" d="M176.708 534.398C177.575 543.074 178.347 551.75 179.214 560.427C178.829 566.307 178.057 572.188 178.057 578.069C177.961 659.433 177.961 740.798 178.057 822.163C178.057 823.994 179.118 825.826 179.696 827.658C178.636 828.718 177.575 829.779 176.611 830.839C176.611 732.025 176.708 633.211 176.708 534.398Z" fill="#ADA0CC"/>
<path id="Vector_18" d="M592.496 214.339C593.749 213.086 595.002 211.833 596.352 210.483C595.002 211.736 593.749 212.989 592.496 214.339Z" fill="#95C7DB"/>
<path id="Vector_19" d="M179.693 827.656C179.115 825.825 178.054 823.993 178.054 822.161C177.958 740.797 177.958 659.432 178.054 578.067C178.054 572.187 178.826 566.306 179.211 560.425C215.555 559.365 252.092 555.027 288.051 558.112C321.31 561.004 338.277 542.784 355.437 520.9C355.919 520.804 356.498 520.707 356.98 520.514C363.728 518.008 366.909 521.864 372.79 525.334C395.637 538.638 420.51 531.794 442.393 521.189C457.047 514.152 467.073 518.297 472.182 529.191C485.486 558.015 479.702 589.732 483.172 620.099C487.125 654.226 466.494 646.032 448.852 646.128C437.091 646.225 425.426 646.321 413.665 646.032C411.158 645.936 408.266 644.875 406.338 643.236C393.324 632.825 377.996 645.453 364.692 638.898C350.424 631.861 340.88 649.599 327.769 645.743C323.72 644.586 320.732 636.777 317.261 636.874C296.438 637.259 275.422 637.356 257.78 651.816C250.164 658.082 237.343 659.432 236.764 671.964C235.8 691.342 235.993 710.815 236.089 730.192C236.186 748.027 236.379 748.027 246.597 751.69C247.176 751.883 247.32 753.346 247.609 754.214C235.462 767.036 223.364 779.84 211.217 792.566C200.709 804.327 190.201 815.992 179.693 827.656Z" fill="#6E5EA6" stroke-width="1.0759" stroke="#6E5EA6"/>
<path id="Vector_20" d="M247.561 754.292C247.272 753.424 247.176 751.978 246.597 751.785C236.378 748.122 236.186 748.218 236.089 730.287C235.993 710.91 235.896 691.436 236.764 672.059C237.342 659.527 250.164 658.177 257.78 651.911C275.422 637.45 296.438 637.258 317.261 636.968C320.635 636.872 323.72 644.681 327.769 645.838C340.88 649.694 350.424 631.955 364.692 638.993C377.899 645.548 393.324 632.919 406.338 643.331C408.266 644.874 411.158 646.03 413.665 646.127C425.426 646.416 437.091 646.32 448.852 646.223C466.494 646.127 487.028 654.321 483.172 620.194C479.701 589.827 485.485 558.11 472.182 529.285C467.169 518.392 457.046 514.246 442.393 521.284C420.509 531.888 395.637 538.733 372.79 525.429C366.909 521.959 363.728 518.103 356.979 520.609C360.932 508.655 364.873 496.519 368.825 484.565C369.5 484.372 370.195 484.264 370.87 484.071C387.644 485.421 405.078 480.867 420.985 490.025C424.841 490.603 428.724 491.288 432.58 491.866C435.183 496.301 485.511 497.789 490.252 493.734C494.109 491.131 498.127 488.342 501.983 485.74C511.045 540.4 508.815 595.997 511.707 651.043C494.74 656.153 478.93 663.672 465.434 675.433C449.238 674.759 432.656 675.723 416.846 672.927C394.288 668.974 376.935 678.711 359.486 689.798C352.256 694.329 344.35 695.485 336.735 693.268C310.609 685.845 286.797 701.752 288.147 728.166C289.111 745.905 286.797 756.413 265.589 750.436C261.25 749.182 255.948 751.207 251.128 751.689C249.971 752.46 248.718 753.328 247.561 754.292Z" fill="#797AAA" stroke-width="1.0759" stroke="#797AAA"/>
<path id="Vector_21" d="M251.227 751.596C256.047 751.114 261.349 749.089 265.687 750.342C286.993 756.319 289.21 745.908 288.246 728.073C286.896 701.658 310.612 685.752 336.833 693.175C344.449 695.392 352.354 694.235 359.585 689.704C377.034 678.714 394.386 668.881 416.945 672.834C432.755 675.629 449.337 674.665 465.532 675.34C455.892 682.281 446.155 689.319 436.515 696.26C433.141 698.284 429.863 700.309 426.489 702.237C417.62 702.044 417.716 713.613 413.764 714.577C393.712 719.686 385.517 738.581 370.382 748.993C368.839 746.197 367.779 745.426 364.116 747.45C358.428 750.535 351.872 753.331 345.51 753.62C316.396 754.873 287.378 755.259 258.264 755.548C255.854 755.645 253.541 752.945 251.227 751.596Z" fill="#A29EA1" stroke-width="1.0759" stroke="#A29EA1"/>
<path id="Vector_22" d="M574.95 576.62C601.171 578.741 624.116 570.258 643.975 553.58C649.855 548.663 656.218 550.495 662.581 549.724C669.715 548.856 678.487 553.58 683.404 544.229C684.657 555.026 688.513 566.305 686.392 576.331C684.271 586.068 672.125 581.055 664.605 582.308C651.012 584.622 641.757 591.949 632.888 601.878C627.49 607.855 618.717 610.554 612.258 615.567C588.639 633.981 560.682 615.857 536.099 625.208C548.921 609.012 561.935 592.816 574.95 576.62Z" fill="#F8B570"/>
<path id="Vector_23" d="M251.226 751.595C253.594 752.995 255.939 755.63 258.253 755.533C287.367 755.148 316.48 754.77 345.498 753.613C351.861 753.324 358.398 750.558 364.086 747.377C367.749 745.352 368.954 746.14 370.497 748.935C369.918 749.707 369.321 750.535 368.742 751.306C359.68 761.236 350.715 771.262 341.653 781.191C338.954 784.854 335.676 788.132 333.844 792.181C328.542 803.942 327.674 789.964 323.24 790.157C285.932 791.988 248.455 792.052 211.147 792.63C223.294 779.808 235.381 767.009 247.528 754.283C248.685 753.319 249.973 752.463 251.226 751.595Z" fill="#B59259" stroke-width="1.0759" stroke="#B59259"/>
<path id="Vector_24" d="M341.651 781.095C350.712 771.165 359.678 761.139 368.74 751.21C381.658 757.861 391.395 746.197 403.06 745.04C413.76 743.979 428.125 745.522 424.943 726.241C423.69 718.529 425.811 710.334 426.389 702.333C429.763 700.308 433.041 698.284 436.415 696.356C436.319 705.996 434.776 715.926 436.223 725.373C439.597 747.643 420.991 757.283 408.555 764.61C393.034 773.768 395.637 785.144 394.094 797.29C393.612 801.05 394.094 805.003 393.998 808.859C393.901 813.101 393.516 817.342 393.323 821.488C390.238 816.378 387.346 811.076 383.972 806.16C380.116 800.472 376.741 791.699 371.632 790.446C363.823 788.518 355.822 780.131 346.471 787.554C345.121 788.614 342.904 794.591 339.915 789.289C339.144 787.361 341.072 783.89 341.651 781.095Z" fill="#F8B570"/>
<path id="Vector_25" d="M368.838 484.557C364.886 496.511 361.029 508.561 357.077 520.515C356.595 520.612 356.016 520.708 355.534 520.901C296.439 527.746 237.247 524.854 178.055 522.733C174.103 502.584 181.044 495.836 202.542 497.282C235.994 499.596 269.928 499.981 302.705 491.498C310.128 489.57 321.504 489.088 324.974 478.291C325.071 478.001 325.842 477.809 325.842 477.809C337.507 497.282 354.667 481.857 368.838 484.557Z" fill="#A29EA1" stroke-width="1.0759" stroke="#A29EA1"/>
<path id="Vector_26" d="M490.307 493.714C486.162 497.955 435.26 496.317 432.561 491.882C451.842 492.46 471.123 493.135 490.307 493.714Z" fill="#7D89C1" stroke-width="1.0759" stroke="#7D89C1"/>
<path id="Vector_27" d="M432.557 491.884C428.7 491.306 424.844 490.631 420.988 490.052C424.844 490.631 428.7 491.306 432.557 491.884Z" fill="#7D89C1"/>
</g>
</svg>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_5470_4251">
<rect width="1024" height="1024" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

@@ -1,5 +1,13 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg x="94.969" y="72.212" width="840" height="809.053" viewBox="0 0 840 809.053" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M838.697 289.781C839.129 290.058 839.092 290.701 838.633 290.929C835.039 292.719 831.485 294.6 827.806 296.232C799.555 308.776 772.522 323.383 749.552 344.372C741.699 351.546 734.598 359.69 727.95 368.025C721.643 375.923 719.536 385.597 719.632 395.64C719.651 397.39 719.936 399.14 720.232 401.108C720.435 402.455 721.585 403.47 722.947 403.47H754.934C756.426 403.47 757.641 404.683 757.574 406.173C757.007 418.72 752.651 429.514 745.064 438.533C734.995 450.489 730.891 464.7 726.144 478.966C719.221 499.763 718.756 521.203 720.398 542.478C722.108 564.655 713.899 582.337 700.656 598.83C690.259 611.784 680.19 625.011 669.847 638.019C659.245 651.356 655.291 666.414 656.426 683.344C657.89 705.043 662.979 725.963 667.727 747.088C671.598 764.333 672.871 782.151 675.21 799.723C675.317 800.514 675.272 801.319 675.175 802.406C675.006 804.292 672.993 805.496 671.237 804.786C667.425 803.245 663.961 801.983 661.064 799.915C647.862 790.5 639.736 777.082 633.087 762.666C629.837 755.612 626.686 748.51 623.528 741.499C623.236 740.852 622.7 740.346 622.039 740.087C612.637 736.391 610.069 734.129 607.43 727.667C607.013 726.646 606.042 725.945 604.94 725.931C590.847 725.748 576.824 727.122 563.328 731.798C539.442 740.065 515.514 748.25 491.969 757.391C465.73 767.585 438.573 769.648 411.239 766.355C385.068 763.199 359.197 757.514 333.176 753.101C324.448 751.625 315.61 750.75 306.841 749.425C304.666 749.097 302.504 748.25 300.507 747.28C284.746 739.6 269.178 731.511 253.267 724.159C236.015 716.193 222.69 721.877 216.301 739.559C213.675 746.829 212.115 754.467 209.57 761.764C205.685 772.9 201.622 784.009 197.148 794.914C193.962 802.692 187.887 806.658 178.915 807.795C177.278 808.003 175.934 806.595 176.072 804.951C176.687 797.655 177.055 790.615 178.241 783.722C180.676 769.607 184.781 755.724 186.272 741.527C187.749 727.343 187.024 712.914 186.86 698.607C186.833 696.53 184.917 694.125 183.289 692.458C171.62 680.597 159.95 668.737 147.993 657.177C135.338 644.933 123.039 632.28 114.584 616.812C106.896 602.751 94.8841 600.743 81.5727 603.339C60.1486 607.52 38.9844 612.959 17.7107 617.905C12.3941 619.143 7.11322 620.525 1.82357 621.849C1.16721 622.013 0.484001 621.707 0.168963 621.108C-0.12378 620.551 -0.0300104 619.87 0.401242 619.413C2.93975 616.72 5.31065 614.184 8.16154 611.811C24.2638 598.42 40.7355 585.452 56.6463 571.843C66.1408 563.712 74.9923 554.803 83.7753 545.894C85.5401 544.104 86.6483 540.674 86.4704 538.105C82.0652 474.457 96.8679 414.73 123.327 357.408C134.518 333.153 146.142 309.094 157.878 284.322C158.345 283.335 159.369 282.751 160.459 282.687C170.461 282.106 171.998 271.978 175.957 264.599C178.669 259.543 180.643 254.095 182.891 248.386C183.584 246.625 185.831 246.144 187.204 247.447C188.931 249.086 190.564 250.638 192.338 252.327C193.394 253.332 195.059 253.317 196.13 252.327C199.595 249.128 203.201 246.202 205.753 242.545C215.658 228.389 225.399 214.096 234.415 199.379C238.574 192.588 243.198 188.325 251.16 186.097C277.787 178.649 304.16 170.335 330.424 162.392C331.068 162.197 331.64 161.784 332.011 161.223C332.138 161.03 332.228 160.859 332.246 160.682C334.189 145.022 335.461 143.71 351.303 141.114C368.172 138.354 378.802 129.336 381.647 112.282C384.479 95.2703 385.314 77.9165 386.969 60.6994C388.351 46.2015 389.377 31.6626 391.087 17.2057C392.145 8.24311 394.608 3.97571 400.598 0.355299C401.894 -0.42824 403.557 0.142784 404.243 1.49332C407.282 7.4772 410.711 13.3289 412.744 19.6243C418.257 36.7184 422.635 54.1678 427.998 71.3166C432.608 86.0468 437.465 100.736 443.047 115.125C450.517 134.405 465.798 142.576 485.854 142.918C493.611 143.055 501.368 143.328 509.111 142.986C518.592 142.576 527.403 144.653 534.996 150.092C555.53 164.822 576.257 179.32 596.012 195.048C610.924 206.928 624.546 220.435 639.396 233.918C640.083 234.542 641.043 234.751 641.954 234.576C649.443 233.136 656.045 237.059 662.73 241.666C663.822 242.419 664.193 243.86 663.612 245.053C661.429 249.534 659.269 253.957 656.99 258.64C656.297 260.065 656.98 261.773 658.432 262.408C673.511 269.004 684.03 280.872 695.731 291.217C699.822 294.825 704.213 298.118 708.55 301.438C716.718 307.697 725.761 308.995 735.693 306.494C754.901 301.657 774.095 296.751 793.44 292.556C808.443 289.301 822.647 287.5 838.432 289.683C838.526 289.696 838.617 289.73 838.697 289.781ZM432.82 707.072C432.825 707.068 432.831 707.065 432.837 707.065C448.553 707.065 464.269 707.243 479.985 706.956C484.185 706.874 488.809 706.259 492.489 704.401C512.19 694.467 532.013 684.724 551.07 673.656C570.73 662.246 572.385 656.575 566.557 634.344C561.304 614.325 551.604 596.22 541.343 578.511C529.209 557.55 519.741 536.083 519.139 511.268C518.825 498.246 515.076 485.757 508.879 474.074C503.393 463.73 494.897 458.838 483.446 458.688C451.775 458.278 420.761 462.063 390.595 472.27C312.614 498.684 265.114 562.824 263.801 642.911C263.486 662.232 271.859 675.35 288.755 683.002C300.534 688.345 312.806 692.786 325.173 696.585C360.261 707.365 396.485 708.363 432.804 707.079C432.81 707.079 432.81Line truncated
</svg>
<g id="AppIcon / macOS / mono &#194;&#183; light">
<g clip-path="url(#clip0_5472_439335)">
<path id="02-mark-mono" d="M846.215 406.917C846.56 407.138 846.531 407.651 846.164 407.833C843.297 409.261 840.463 410.762 837.528 412.063C814.995 422.068 793.433 433.719 775.111 450.46C768.848 456.182 763.185 462.678 757.881 469.326C752.851 475.626 751.17 483.342 751.247 491.353C751.263 492.749 751.49 494.145 751.726 495.714C751.887 496.788 752.805 497.598 753.891 497.598H779.404C780.594 497.598 781.564 498.565 781.51 499.754C781.058 509.761 777.584 518.371 771.532 525.565C763.501 535.101 760.227 546.436 756.441 557.815C750.919 574.403 750.548 591.503 751.858 608.473C753.222 626.162 746.675 640.265 736.112 653.42C727.819 663.753 719.787 674.303 711.538 684.679C703.081 695.316 699.927 707.327 700.833 720.83C702.001 738.138 706.06 754.824 709.846 771.674C712.935 785.428 713.949 799.641 715.815 813.657C715.901 814.287 715.864 814.929 715.787 815.796C715.653 817.301 714.047 818.261 712.646 817.695C709.606 816.465 706.843 815.459 704.532 813.809C694.002 806.3 687.52 795.597 682.217 784.099C679.625 778.473 677.112 772.808 674.593 767.216C674.36 766.699 673.932 766.297 673.405 766.089C665.906 763.141 663.858 761.337 661.752 756.183C661.42 755.369 660.646 754.81 659.766 754.798C648.525 754.652 637.34 755.748 626.577 759.478C607.524 766.072 588.439 772.6 569.659 779.892C548.73 788.022 527.07 789.668 505.267 787.041C484.393 784.524 463.758 779.99 443.003 776.469C436.041 775.292 428.992 774.595 421.997 773.538C420.262 773.276 418.538 772.6 416.945 771.826C404.374 765.701 391.957 759.249 379.266 753.385C365.506 747.031 354.877 751.565 349.781 765.669C347.686 771.467 346.442 777.559 344.413 783.379C341.314 792.262 338.073 801.123 334.505 809.82C331.963 816.025 327.118 819.188 319.961 820.095C318.656 820.26 317.583 819.138 317.694 817.827C318.185 812.007 318.478 806.392 319.424 800.894C321.366 789.635 324.64 778.562 325.829 767.238C327.008 755.925 326.43 744.416 326.299 733.004C326.277 731.348 324.749 729.43 323.451 728.1C314.143 718.64 304.835 709.179 295.298 699.959C285.204 690.193 275.394 680.101 268.65 667.763C262.518 656.548 252.937 654.946 242.32 657.017C225.231 660.352 208.35 664.69 191.382 668.635C187.141 669.623 182.929 670.725 178.71 671.781C178.187 671.912 177.642 671.667 177.39 671.19C177.157 670.746 177.232 670.203 177.576 669.838C179.6 667.69 181.492 665.667 183.765 663.774C196.609 653.093 209.747 642.75 222.438 631.895C230.011 625.41 237.071 618.304 244.076 611.198C245.484 609.77 246.368 607.034 246.226 604.985C242.712 554.218 254.519 506.579 275.623 460.858C284.55 441.511 293.821 422.322 303.182 402.563C303.555 401.776 304.371 401.31 305.241 401.26C313.219 400.796 314.444 392.717 317.602 386.832C319.765 382.799 321.34 378.454 323.132 373.9C323.685 372.496 325.478 372.112 326.573 373.151C327.95 374.459 329.253 375.696 330.668 377.043C331.511 377.845 332.838 377.833 333.693 377.044C336.456 374.492 339.332 372.158 341.368 369.241C349.269 357.95 357.038 346.549 364.229 334.811C367.546 329.395 371.235 325.994 377.585 324.218C398.823 318.277 419.859 311.645 440.808 305.309C441.322 305.154 441.778 304.825 442.074 304.377C442.175 304.223 442.246 304.087 442.261 303.945C443.811 291.455 444.825 290.409 457.462 288.338C470.916 286.137 479.395 278.943 481.664 265.341C483.923 251.772 484.589 237.931 485.909 224.198C487.011 212.634 487.83 201.038 489.194 189.507C490.038 182.358 492.002 178.954 496.78 176.066C497.814 175.441 499.14 175.897 499.687 176.974C502.111 181.747 504.846 186.414 506.468 191.436C510.865 205.07 514.357 218.988 518.635 232.666C522.312 244.415 526.186 256.132 530.638 267.608C536.596 282.987 548.785 289.504 564.782 289.777C570.969 289.886 577.156 290.104 583.332 289.831C590.894 289.504 597.922 291.161 603.978 295.499C620.357 307.248 636.888 318.812 652.645 331.356C664.54 340.833 675.405 351.606 687.249 362.36C687.798 362.858 688.563 363.024 689.29 362.885C695.263 361.736 700.529 364.865 705.861 368.54C706.732 369.141 707.028 370.29 706.565 371.241C704.823 374.815 703.1 378.343 701.283 382.079C700.73 383.215 701.275 384.578 702.433 385.084C714.46 390.345 722.85 399.811 732.183 408.063C735.446 410.94 738.949 413.567 742.408 416.216C748.922 421.207 756.135 422.243 764.057 420.248C779.378 416.39 794.688 412.477 810.117 409.131C822.084 406.534 833.414 405.098 846.004 406.839C846.079 406.85 846.151 406.876 846.215 406.917ZM522.481 739.756C522.484 739.753 522.489 739.751 522.494 739.751C535.03 739.751 547.565 739.893 560.1 739.664C563.45 739.598 567.139 739.108 570.074 737.626C585.787 729.702 601.599 721.931 616.799 713.103C632.48 704.002 633.8 699.479 629.152 681.747C624.962 665.78 617.225 651.339 609.041 637.214C599.362 620.495 591.811 603.372 591.331 583.58C591.08 573.193 588.09 563.232 583.147 553.913C578.771 545.662 571.994 541.761 562.861 541.641C537.6 541.314 512.862 544.333 488.801 552.474C426.602 573.542 388.716 624.702 387.668 688.58C387.417 703.991 394.095 714.454 407.572 720.558C416.967 724.819 426.755 728.362 436.62 731.391C464.607 739.99 493.499 740.786 522.468 739.762C522.473 739.762 522.47Line truncated
</g>
</g>
<defs>
<clipPath id="clip0_5472_439335">
<rect width="1024" height="1024" rx="230" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

+1 -14
View File
@@ -1,19 +1,6 @@
{
"fill" : {
"linear-gradient" : [
"srgb:0.24706,0.24706,0.27451,1.00000",
"srgb:0.03529,0.03529,0.04314,1.00000"
],
"orientation" : {
"start" : {
"x" : 0.5,
"y" : 0
},
"stop" : {
"x" : 0.5,
"y" : 1
}
}
"solid" : "srgb:0.12157,0.12157,0.13725,1.00000"
},
"groups" : [
{
Binary file not shown.
-2
View File
@@ -44,8 +44,6 @@
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIcons</key>
-24
View File
@@ -197,29 +197,6 @@ if(NOT ANDROID)
test_common
)
qt6_add_resources(XRAY_PAGES_UI_QRC
${CLIENT_QML_QRC_FILE}
../images/images.qrc
)
add_executable(test_xray_pages_ui
xrayPagesUi/testXrayPagesUi.h
xrayPagesUi/main.cpp
xrayPagesUi/pageMain.cpp
xrayPagesUi/pageTransport.cpp
xrayPagesUi/pageSecurity.cpp
xrayPagesUi/pageFlowPadding.cpp
xrayPagesUi/pageXmux.cpp
xrayPagesUi/saveFlowSnapshots.cpp
xrayPagesUi/knownIssues.cpp
${XRAY_PAGES_UI_QRC}
)
target_link_libraries(test_xray_pages_ui PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_language_model_and_controller
testUiLanguageModelAndController.cpp
)
@@ -505,7 +482,6 @@ if(NOT ANDROID)
add_test(NAME XrayGeneratorMatrixTest COMMAND test_xray_generator_matrix)
add_test(NAME XrayConfigModelTest COMMAND test_xray_config_model)
add_test(NAME XrayCoreAcceptsTest COMMAND test_xray_core_accepts)
add_test(NAME XrayPagesUiTest COMMAND test_xray_pages_ui)
add_test(NAME ConfigFormatVersionTest COMMAND test_config_format_version)
add_test(NAME UiLanguageModelAndControllerTest COMMAND test_ui_language_model_and_controller)
add_test(NAME UiIpModelAndControllerTest COMMAND test_ui_ip_model_and_controller)
-108
View File
@@ -1,108 +0,0 @@
#include "testXrayPagesUi.h"
using namespace amnezia;
void TestXrayPagesUi::issueTransportRoundTripLeavesSaveEnabled()
{
storeAndOpen(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
click(typed("RadioItemType", "text", QStringLiteral("XHTTP")));
click(typed("RadioItemType", "text", QStringLiteral("RAW (TCP)")));
QCOMPARE(m_model->transport(), QStringLiteral("raw"));
QVERIFY2(!m_model->hasUnsavedChanges(), "raw -> XHTTP -> raw left the model dirty");
QVERIFY2(!saveButton()->isEnabled(), "Save is enabled although nothing was changed");
}
void TestXrayPagesUi::issueSubpageSavesEmptyPort()
{
XrayServerConfig srv = xraytest::baseConfig("xhttp", "reality", "");
srv.port = QStringLiteral("8443");
storeAndOpen(srv);
m_model->setPort(QString());
m_model->setXhttpUplinkMethod(QStringLiteral("PUT"));
XRAY_LOAD("PageProtocolXrayTransportSettings");
XRAY_QUESTIONS(questions);
QQuickItem *save = saveButton();
if (save->isEnabled())
click(save);
const bool proceeded = questions.count() > 0;
if (proceeded)
answer(questions, false);
QVERIFY2(!proceeded, "a sub-page offered to save with an empty port");
}
void TestXrayPagesUi::issueHiddenRealitySniBlocksMkcpSave()
{
XrayServerConfig srv = xraytest::baseConfig("raw", "reality", "");
srv.sni = QStringLiteral("*.asd.com");
srv.site = srv.sni;
openStored(srv);
XRAY_LOAD("PageProtocolXrayTransportSettings");
click(typed("RadioItemType", "text", QStringLiteral("mKCP")));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
if (questions.count() > 0)
answer(questions, false);
QVERIFY2(errors.isEmpty(), qPrintable(QStringLiteral("refused with: ") + (errors.isEmpty() ? QString()
: errors.first().first().toString())));
}
void TestXrayPagesUi::issueClearingXmuxZeroEnablesSave()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(minMaxInput(minMaxRow(0), QStringLiteral("Min")), QString());
QCOMPARE(m_model->xmuxMaxConcurrencyMin(), QString());
QVERIFY2(!m_model->hasUnsavedChanges(), "clearing a 0 field counts as an unsaved change");
QVERIFY2(!saveButton()->isEnabled(), "Save is enabled for a no-op");
}
void TestXrayPagesUi::issueSnapshotWithoutPortApplyIsSilent()
{
const QString snapshot = QStringLiteral(
R"({"displayName":"no port","serverConfig":{"xray_transport":"raw","xray_security":"tls"}})");
QVERIFY(m_snapshots->importFromJson(snapshot));
XRAY_LOAD("PageProtocolXraySnapshots");
QCOMPARE(m_model->transport(), QStringLiteral("xhttp"));
QObject *drawer = popupOf(m_page);
click(typed("ListItemType", "text", QStringLiteral("no port")));
QVERIFY(QTest::qWaitFor([drawer] { return drawer->property("opened").toBool(); }, 3000));
QTest::qWait(250);
const QString portBefore = m_model->port();
click(typed("ListItemType", "text", QStringLiteral("Apply configuration"), popupContent(drawer)));
QCOMPARE(m_model->transport(), QStringLiteral("raw"));
QCOMPARE(m_model->security(), QStringLiteral("tls"));
QCOMPARE(m_model->port(), portBefore);
}
void TestXrayPagesUi::issueSaveWithoutReturnSkipsClamp()
{
openStored(xraytest::baseConfig("mkcp", "none", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
clearAndType(field(QStringLiteral("TTI")), QStringLiteral("5"), false);
QCOMPARE(m_model->mkcpTti(), QStringLiteral("5"));
XRAY_QUESTIONS(questions);
click(saveButton());
if (questions.count() > 0)
answer(questions, false);
QCOMPARE(m_model->mkcpTti(), QStringLiteral("10"));
}
void TestXrayPagesUi::issueSaveWithoutReturnKeepsRawKeepAlive()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(field(QStringLiteral("hKeepAlivePeriod")), QStringLiteral("-"), false);
QCOMPARE(m_model->xmuxHKeepAlivePeriod(), QStringLiteral("-"));
XRAY_QUESTIONS(questions);
click(saveButton());
if (questions.count() > 0)
answer(questions, false);
QCOMPARE(m_model->xmuxHKeepAlivePeriod(), QString());
}
-270
View File
@@ -1,270 +0,0 @@
#include <QApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickStyle>
#include <QUuid>
#include "testXrayPagesUi.h"
#include "ui/utils/pageEnum.h"
#include "vpnConnection.h"
using namespace amnezia;
void TestXrayPagesUi::initTestCase()
{
QQuickStyle::setStyle(QStringLiteral("Basic"));
PageLoader::declareQmlPageEnum();
for (const char *name : { "ProtocolEnum", "TransportProto", "ServiceType" })
qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, name,
QStringLiteral("Error: only enums"));
m_settings = new SecureQSettings("AmneziaVPN-Test-" + QUuid::createUuid().toString(), "amnezia-client", nullptr, false);
m_engine = new QQmlApplicationEngine(this);
m_engine->addImportPath(QStringLiteral("qrc:/ui/qml/Modules/"));
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_core = new TestCoreController(vpnConnection, m_settings, m_engine, this);
m_model = qobject_cast<XrayConfigModel *>(
m_engine->rootContext()->contextProperty(QStringLiteral("XrayConfigModel")).value<QObject *>());
QVERIFY2(m_model, "XrayConfigModel is not exposed to QML");
m_snapshots = qobject_cast<XrayConfigSnapshotsModel *>(
m_engine->rootContext()->contextProperty(QStringLiteral("XrayConfigSnapshotsModel")).value<QObject *>());
QVERIFY2(m_snapshots, "XrayConfigSnapshotsModel is not exposed to QML");
m_pages = m_core->pageController();
QVERIFY(m_pages);
m_serverId = addServer(QStringLiteral("203.0.113.10"), QStringLiteral("not-used"),
serverConfigUtils::ConfigType::SelfHostedAdmin);
m_readOnlyServerId = addServer(QStringLiteral("203.0.113.11"), QString(), serverConfigUtils::ConfigType::SelfHostedUser);
m_hostlessServerId = addServer(QString(), QStringLiteral("not-used"), serverConfigUtils::ConfigType::SelfHostedAdmin);
QVERIFY2(!m_serverId.isEmpty() && !m_readOnlyServerId.isEmpty() && !m_hostlessServerId.isEmpty(),
"test servers were not added");
selectServer(m_readOnlyServerId);
QVERIFY2(!m_core->m_serversUiController->isProcessedServerHasWriteAccess(), "read-only server must not be editable");
selectServer(m_hostlessServerId);
QVERIFY2(m_core->m_serversUiController->isProcessedServerHasWriteAccess(), "hostless server must be editable");
selectServer(m_serverId);
QVERIFY2(m_core->m_serversUiController->isProcessedServerHasWriteAccess(), "test server must be editable");
}
void TestXrayPagesUi::init()
{
selectServer(m_serverId);
storeAndOpen(xraytest::baseConfig("xhttp", "reality", ""));
m_core->m_appSettingsRepository->setXraySavedConfigs(QByteArray());
m_snapshots->reload();
}
void TestXrayPagesUi::cleanup()
{
closePage();
}
void TestXrayPagesUi::cleanupTestCase()
{
closePage();
m_settings->clearSettings();
delete m_core;
delete m_engine;
delete m_settings;
}
void TestXrayPagesUi::transportRadioClickChangesModel()
{
openStored(xraytest::baseConfig("xhttp", "reality", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
for (const auto &pair : { qMakePair(QStringLiteral("RAW (TCP)"), QStringLiteral("raw")),
qMakePair(QStringLiteral("mKCP"), QStringLiteral("mkcp")),
qMakePair(QStringLiteral("XHTTP"), QStringLiteral("xhttp")) }) {
QQuickItem *radio = item("text", pair.first);
QVERIFY(radio);
click(radio);
QCOMPARE(m_model->transport(), pair.second);
}
}
void TestXrayPagesUi::packetUpFieldsFollowMode_data()
{
QTest::addColumn<QString>("mode");
QTest::addColumn<QString>("security");
QTest::addColumn<bool>("visible");
QTest::newRow("stream-one + reality") << "Stream-one" << "reality" << false;
QTest::newRow("stream-up + tls") << "Stream-up" << "tls" << false;
QTest::newRow("packet-up + tls") << "Packet-up" << "tls" << true;
QTest::newRow("auto + none") << "Auto" << "none" << true;
QTest::newRow("auto + reality") << "Auto" << "reality" << false;
}
void TestXrayPagesUi::packetUpFieldsFollowMode()
{
QFETCH(QString, mode);
QFETCH(QString, security);
QFETCH(bool, visible);
XrayServerConfig srv = xraytest::baseConfig("xhttp", security, "");
srv.xhttp.mode = mode;
openStored(srv);
XRAY_LOAD("PageProtocolXrayTransportSettings");
for (const QString &header : { QStringLiteral("SeqKey"), QStringLiteral("UplinkDataKey") }) {
QQuickItem *f = item("headerText", header);
QVERIFY(f);
QCOMPARE(f->isVisible(), visible);
}
for (const QString &header : { QStringLiteral("SeqPlacement"), QStringLiteral("UplinkDataPlacement") }) {
QQuickItem *dd = item("headerText", header);
QVERIFY(dd);
QCOMPARE(dd->isVisible(), visible);
}
}
void TestXrayPagesUi::pathGetsLeadingSlash()
{
openStored(xraytest::baseConfig("xhttp", "reality", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *textField = field(QStringLiteral("Path"));
QVERIFY(textField);
clearAndType(textField, QStringLiteral("amnezia"));
QCOMPARE(m_model->xhttpPath(), QStringLiteral("/amnezia"));
QCOMPARE(textField->property("text").toString(), QStringLiteral("/amnezia"));
}
void TestXrayPagesUi::visionNeedsTlsOrReality_data()
{
QTest::addColumn<QString>("security");
QTest::addColumn<bool>("enabled");
QTest::newRow("none") << "none" << false;
QTest::newRow("tls") << "tls" << true;
QTest::newRow("reality") << "reality" << true;
}
void TestXrayPagesUi::visionNeedsTlsOrReality()
{
QFETCH(QString, security);
QFETCH(bool, enabled);
openStored(xraytest::baseConfig("raw", security, ""));
XRAY_LOAD("PageProtocolXrayFlowSettings");
for (const QString &flow : { QStringLiteral("xtls-rprx-vision"), QStringLiteral("xtls-rprx-vision-udp443") }) {
QQuickItem *radio = item("text", flow);
QVERIFY(radio);
QCOMPARE(radio->isEnabled(), enabled);
}
}
void TestXrayPagesUi::fingerprintHiddenOnMkcp_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<bool>("visible");
QTest::newRow("raw + tls") << "raw" << true;
QTest::newRow("mkcp + tls") << "mkcp" << false;
}
void TestXrayPagesUi::fingerprintHiddenOnMkcp()
{
QFETCH(QString, transport);
QFETCH(bool, visible);
openStored(xraytest::baseConfig(transport, "tls", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *dd = item("descriptionText", QStringLiteral("Fingerprint"));
QVERIFY(dd);
QCOMPARE(dd->isVisible(), visible);
}
void TestXrayPagesUi::sniRejectsWildcardInput()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *textField = visibleField(QStringLiteral("Server Name (SNI)"));
QVERIFY(textField);
clearAndType(textField, QStringLiteral("*.example.com"));
QVERIFY2(!textField->property("text").toString().contains(QLatin1Char('*')),
qPrintable(textField->property("text").toString()));
QVERIFY(!m_model->sni().contains(QLatin1Char('*')));
}
void TestXrayPagesUi::xPaddingFieldsFollowObfsMode_data()
{
QTest::addColumn<bool>("obfs");
QTest::newRow("obfs on") << true;
QTest::newRow("obfs off") << false;
}
void TestXrayPagesUi::xPaddingFieldsFollowObfsMode()
{
QFETCH(bool, obfs);
XrayServerConfig srv = xraytest::baseConfig("xhttp", "reality", "");
srv.xhttp.xPadding.obfsMode = obfs;
srv.xhttp.xPadding.bytesMin.clear();
srv.xhttp.xPadding.bytesMax.clear();
openStored(srv);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *bytes = item("text", QStringLiteral("xPaddingBytes"));
QVERIFY(bytes);
QCOMPARE(bytes->isEnabled(), obfs);
QCOMPARE(bytes->property("subtitle").toString(), QStringLiteral("Default"));
for (const QString &header : { QStringLiteral("xPaddingKey"), QStringLiteral("xPaddingHeader"),
QStringLiteral("xPaddingPlacement"), QStringLiteral("xPaddingMethod") }) {
QQuickItem *f = item("headerText", header);
QVERIFY(f);
QCOMPARE(f->isEnabled(), obfs);
}
}
void TestXrayPagesUi::subpageSaveRefusesInvalidSni_data()
{
QTest::addColumn<QString>("page");
QTest::newRow("flow") << "PageProtocolXrayFlowSettings";
QTest::newRow("xmux") << "PageProtocolXrayXmuxSettings";
QTest::newRow("xPadding") << "PageProtocolXrayXPaddingSettings";
QTest::newRow("xPaddingBytes") << "PageProtocolXrayXPaddingBytesSettings";
QTest::newRow("security") << "PageProtocolXraySecuritySettings";
QTest::newRow("transport") << "PageProtocolXrayTransportSettings";
}
void TestXrayPagesUi::subpageSaveRefusesInvalidSni()
{
QFETCH(QString, page);
openStored(xraytest::baseConfig("xhttp", "reality", ""));
m_model->setSni(QStringLiteral("*"));
QVERIFY(m_model->hasUnsavedChanges());
loadPage(page);
if (QTest::currentTestFailed())
return;
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
QQuickItem *save = saveButton();
QVERIFY(save);
QVERIFY2(save->isVisible() && save->isEnabled(), "Save must be shown for an edited, writable server");
click(save);
QCOMPARE(errors.count(), 1);
QVERIFY2(errors.first().first().toString().contains(QLatin1String("SNI")),
qPrintable(errors.first().first().toString()));
}
int main(int argc, char *argv[])
{
#ifndef Q_OS_MACOS
if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM"))
qputenv("QT_QPA_PLATFORM", "offscreen");
#endif
QApplication app(argc, argv);
TestXrayPagesUi test;
QStringList args = app.arguments();
if (!args.contains(QStringLiteral("-maxwarnings")))
args << QStringLiteral("-maxwarnings") << QStringLiteral("0");
return QTest::qExec(&test, args);
}
@@ -1,394 +0,0 @@
#include "testXrayPagesUi.h"
#include "ui/utils/pageEnum.h"
using namespace amnezia;
namespace
{
const QString kVision = QStringLiteral("xtls-rprx-vision");
const QString kUdp443 = QStringLiteral("xtls-rprx-vision-udp443");
const QString kVisionHint =
QStringLiteral("xtls-rprx-vision is available only with the RAW (TCP) transport and TLS or Reality security.");
const QString kDeviceOnlyText =
QStringLiteral("The server will not be changed now. The new settings apply the next time you connect.");
const QString kServerBreaksSharedText = QStringLiteral(
"The server configuration will be updated. All users with whom you shared a connection with will no "
"longer be able to connect to it. You will need to share the connection again.");
}
void TestXrayPagesUi::flowRadioRoundTrip()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXrayFlowSettings");
auto checkedOnly = [this](const QString &text) {
for (const QString &t : { QStringLiteral("Empty"), kVision, kUdp443 }) {
if (typed("RadioItemType", "text", t)->property("checked").toBool() != (t == text))
return false;
}
return true;
};
QVERIFY(checkedOnly(QStringLiteral("Empty")));
QVERIFY(!saveButton()->isEnabled());
click(typed("RadioItemType", "text", kVision));
QCOMPARE(m_model->flow(), kVision);
QVERIFY(checkedOnly(kVision));
QVERIFY(saveButton()->isEnabled());
click(typed("RadioItemType", "text", kUdp443));
QCOMPARE(m_model->flow(), kUdp443);
QVERIFY(checkedOnly(kUdp443));
click(typed("RadioItemType", "text", QStringLiteral("Empty")));
QCOMPARE(m_model->flow(), QString());
QVERIFY(checkedOnly(QStringLiteral("Empty")));
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::flowUdp443Subtitle()
{
XRAY_LOAD("PageProtocolXrayFlowSettings");
QCOMPARE(typed("RadioItemType", "text", kUdp443)->property("subtitle").toString(),
QStringLiteral("For apps that require QUIC. Browsers may stop loading."));
}
void TestXrayPagesUi::flowVisionNotAllowed_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("flow");
QTest::newRow("xhttp + reality + vision") << "xhttp" << "reality" << kVision;
QTest::newRow("mkcp + tls + vision") << "mkcp" << "tls" << kVision;
QTest::newRow("raw + none + udp443") << "raw" << "none" << kUdp443;
QTest::newRow("mkcp + none + empty") << "mkcp" << "none" << "";
}
void TestXrayPagesUi::flowVisionNotAllowed()
{
QFETCH(QString, transport);
QFETCH(QString, security);
QFETCH(QString, flow);
openStored(xraytest::baseConfig(transport, security, flow));
XRAY_LOAD("PageProtocolXrayFlowSettings");
QVERIFY(typed("RadioItemType", "text", QStringLiteral("Empty"))->property("checked").toBool());
for (const QString &t : { kVision, kUdp443 }) {
QQuickItem *radio = typed("RadioItemType", "text", t);
QVERIFY2(!radio->isEnabled(), qPrintable(t));
QVERIFY2(!radio->property("checked").toBool(), qPrintable(t));
}
QQuickItem *hint = typed("SmallTextType", "text", kVisionHint);
QVERIFY(hint && hint->isVisible());
QCOMPARE(m_model->flow(), flow);
QVERIFY(!m_model->hasUnsavedChanges());
click(typed("RadioItemType", "text", kVision));
QCOMPARE(m_model->flow(), flow);
}
void TestXrayPagesUi::flowEmptyWhenVisionNotAllowed()
{
openStored(xraytest::baseConfig("xhttp", "reality", kVision));
XRAY_LOAD("PageProtocolXrayFlowSettings");
click(typed("RadioItemType", "text", QStringLiteral("Empty")));
QCOMPARE(m_model->flow(), QString());
QVERIFY(saveButton()->isEnabled());
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(1).toString(), kDeviceOnlyText);
answer(questions, false);
}
void TestXrayPagesUi::flowDeviceOrServerChange_data()
{
QTest::addColumn<QString>("from");
QTest::addColumn<QString>("to");
QTest::addColumn<QString>("expected");
QTest::newRow("vision -> udp443 is device-only") << kVision << kUdp443 << kDeviceOnlyText;
QTest::newRow("empty -> vision breaks shared configs") << "" << kVision << kServerBreaksSharedText;
}
void TestXrayPagesUi::flowDeviceOrServerChange()
{
QFETCH(QString, from);
QFETCH(QString, to);
QFETCH(QString, expected);
openStored(xraytest::baseConfig("raw", "tls", from));
XRAY_LOAD("PageProtocolXrayFlowSettings");
click(typed("RadioItemType", "text", to));
QCOMPARE(m_model->flow(), to);
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(1).toString(), expected);
answer(questions, false);
}
void TestXrayPagesUi::flowEmptyTransportCountsAsRaw()
{
XrayServerConfig srv = xraytest::baseConfig("raw", "tls", "");
srv.transport.clear();
srv.isThirdPartyConfig = true;
openStored(srv);
XRAY_LOAD("PageProtocolXrayFlowSettings");
QCOMPARE(m_model->transport(), QString());
QVERIFY(typed("RadioItemType", "text", kVision)->isEnabled());
QVERIFY(typed("RadioItemType", "text", kUdp443)->isEnabled());
}
void TestXrayPagesUi::flowUnknownStoredFlow()
{
openStored(xraytest::baseConfig("raw", "tls", "xtls-rprx-direct"));
XRAY_LOAD("PageProtocolXrayFlowSettings");
for (const QString &t : { QStringLiteral("Empty"), kVision, kUdp443 })
QVERIFY2(!typed("RadioItemType", "text", t)->property("checked").toBool(), qPrintable(t));
}
void TestXrayPagesUi::flowKeyboard()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXrayFlowSettings");
QQuickItem *radio = typed("RadioItemType", "text", kVision);
radio->forceActiveFocus();
key(Qt::Key_Return);
QCOMPARE(m_model->flow(), kVision);
}
void TestXrayPagesUi::paddingInitialState()
{
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *obfs = switcherOf(typed("SwitchRowType", "text", QStringLiteral("xPaddingObfsMode")));
QVERIFY(obfs && obfs->property("checked").toBool());
QCOMPARE(typed("DropDownType", "headerText", QStringLiteral("xPaddingPlacement"))->property("text").toString(),
QStringLiteral("Cookie"));
QCOMPARE(typed("DropDownType", "headerText", QStringLiteral("xPaddingMethod"))->property("text").toString(),
QStringLiteral("Repeat-x"));
QQuickItem *keyInput = field(QStringLiteral("xPaddingKey"));
QQuickItem *headerInput = field(QStringLiteral("xPaddingHeader"));
QCOMPARE(keyInput->property("text").toString(), QString());
QCOMPARE(keyInput->property("placeholderText").toString(), QStringLiteral("x_padding"));
QCOMPARE(headerInput->property("text").toString(), QString());
QCOMPARE(headerInput->property("placeholderText").toString(), QStringLiteral("X-Padding"));
}
void TestXrayPagesUi::paddingSwitchTogglesFields()
{
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *obfs = switcherOf(typed("SwitchRowType", "text", QStringLiteral("xPaddingObfsMode")));
QVERIFY(obfs);
auto dependents = [this] {
return QList<QQuickItem *> { typed("ListItemType", "text", QStringLiteral("xPaddingBytes")),
typed("TextFieldType", "headerText", QStringLiteral("xPaddingKey")),
typed("TextFieldType", "headerText", QStringLiteral("xPaddingHeader")),
typed("DropDownType", "headerText", QStringLiteral("xPaddingPlacement")),
typed("DropDownType", "headerText", QStringLiteral("xPaddingMethod")) };
};
click(obfs);
QCOMPARE(m_model->xPaddingObfsMode(), false);
for (QQuickItem *dep : dependents())
QVERIFY(dep && !dep->isEnabled());
QVERIFY(saveButton()->isEnabled());
click(obfs);
QCOMPARE(m_model->xPaddingObfsMode(), true);
for (QQuickItem *dep : dependents())
QVERIFY(dep && dep->isEnabled());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::paddingSwitchLabelToggles()
{
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *label = typed("BodyTextType", "text", QStringLiteral("xPaddingObfsMode"));
QVERIFY(label);
click(label);
QCOMPARE(m_model->xPaddingObfsMode(), false);
}
void TestXrayPagesUi::paddingBytesSubtitle_data()
{
QTest::addColumn<QString>("min");
QTest::addColumn<QString>("max");
QTest::addColumn<QString>("subtitle");
QTest::newRow("both empty") << "" << "" << "Default";
QTest::newRow("min only") << "200" << "" << "200—1000";
QTest::newRow("max only") << "" << "500" << "100—500";
QTest::newRow("both") << "200" << "400" << "200—400";
}
void TestXrayPagesUi::paddingBytesSubtitle()
{
QFETCH(QString, min);
QFETCH(QString, max);
QFETCH(QString, subtitle);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
m_model->setXPaddingBytesMin(min);
m_model->setXPaddingBytesMax(max);
QCOMPARE(typed("ListItemType", "text", QStringLiteral("xPaddingBytes"))->property("subtitle").toString(), subtitle);
}
void TestXrayPagesUi::paddingBytesRowNavigates_data()
{
QTest::addColumn<bool>("obfs");
QTest::newRow("obfs on navigates") << true;
QTest::newRow("obfs off does nothing") << false;
}
void TestXrayPagesUi::paddingBytesRowNavigates()
{
QFETCH(bool, obfs);
XrayServerConfig srv = xraytest::baseConfig("xhttp", "reality", "");
srv.xhttp.xPadding.obfsMode = obfs;
openStored(srv);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QSignalSpy pages(m_pages, &PageController::goToPage);
click(typed("ListItemType", "text", QStringLiteral("xPaddingBytes")));
QCOMPARE(pages.count(), obfs ? 1 : 0);
if (obfs) {
QCOMPARE(qvariant_cast<PageLoader::PageEnum>(pages.first().at(0)),
PageLoader::PageEnum::PageProtocolXrayXPaddingBytesSettings);
}
}
void TestXrayPagesUi::paddingKeyHeaderFilter_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
QTest::newRow("key") << "xPaddingKey" << "my.key name$1" << "mykeyname1";
QTest::newRow("header") << "xPaddingHeader" << "X-Pad.ding" << "X-Padding";
}
void TestXrayPagesUi::paddingKeyHeaderFilter()
{
QFETCH(QString, header);
QFETCH(QString, typed);
QFETCH(QString, expected);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *f = field(header);
clearAndType(f, typed);
QCOMPARE(f->property("text").toString(), expected);
QCOMPARE(header == QLatin1String("xPaddingKey") ? m_model->xPaddingKey() : m_model->xPaddingHeader(), expected);
QVERIFY(saveButton()->isEnabled());
}
void TestXrayPagesUi::paddingDropDownRoundTrip_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("option");
for (const QString o : { QStringLiteral("Cookie"), QStringLiteral("Header"), QStringLiteral("Query"),
QStringLiteral("Query in header") })
QTest::newRow(qPrintable("placement = " + o)) << "xPaddingPlacement" << o;
for (const QString o : { QStringLiteral("Repeat-x"), QStringLiteral("Tokenish") })
QTest::newRow(qPrintable("method = " + o)) << "xPaddingMethod" << o;
}
void TestXrayPagesUi::paddingDropDownRoundTrip()
{
QFETCH(QString, header);
QFETCH(QString, option);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *dd = typed("DropDownType", "headerText", header);
QVERIFY(dd);
const QStringList all = optionNames(dd, false);
const QStringList expected = header == QLatin1String("xPaddingPlacement")
? QStringList { "Cookie", "Header", "Query", "Query in header" }
: QStringList { "Repeat-x", "Tokenish" };
QCOMPARE(all, expected);
QVERIFY(chooseOption(dd, option));
QCOMPARE(header == QLatin1String("xPaddingPlacement") ? m_model->xPaddingPlacement() : m_model->xPaddingMethod(),
option);
QCOMPARE(dd->property("text").toString(), option);
QCOMPARE(optionNames(dd, true), QStringList { option });
}
void TestXrayPagesUi::paddingDisabledDropDownDoesNotOpen()
{
XrayServerConfig srv = xraytest::baseConfig("xhttp", "reality", "");
srv.xhttp.xPadding.obfsMode = false;
openStored(srv);
XRAY_LOAD("PageProtocolXrayXPaddingSettings");
QQuickItem *dd = typed("DropDownType", "headerText", QStringLiteral("xPaddingPlacement"));
QObject *popup = popupOf(dd);
QVERIFY(popup);
click(dd);
QTest::qWait(400);
QVERIFY(!popup->property("opened").toBool());
}
void TestXrayPagesUi::paddingBytesInitialState()
{
XRAY_LOAD("PageProtocolXrayXPaddingBytesSettings");
QQuickItem *row = minMaxRow(0);
QQuickItem *minInput = minMaxInput(row, QStringLiteral("Min"));
QQuickItem *maxInput = minMaxInput(row, QStringLiteral("Max"));
QVERIFY(minInput && maxInput);
QCOMPARE(minInput->property("text").toString(), QString());
QCOMPARE(maxInput->property("text").toString(), QString());
QCOMPARE(minInput->property("placeholderText").toString(), QStringLiteral("100"));
QCOMPARE(maxInput->property("placeholderText").toString(), QStringLiteral("1000"));
QVERIFY(typed("SmallTextType", "text", QStringLiteral("≤ 2147483647"))->isVisible());
QVERIFY(typed("SmallTextType", "text", QStringLiteral("Range"))->isVisible());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::paddingBytesRange_data()
{
QTest::addColumn<QString>("minBefore");
QTest::addColumn<QString>("maxBefore");
QTest::addColumn<QString>("which");
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("minAfter");
QTest::addColumn<QString>("maxAfter");
QTest::newRow("min only") << "" << "" << "Min" << "200" << "200" << "";
QTest::newRow("max only") << "" << "" << "Max" << "400" << "" << "400";
QTest::newRow("min above max placeholder raises max") << "" << "" << "Min" << "2000" << "2000" << "2000";
QTest::newRow("min above max raises max") << "" << "400" << "Min" << "500" << "500" << "500";
QTest::newRow("max below min is raised") << "300" << "" << "Max" << "200" << "300" << "300";
QTest::newRow("max below min placeholder is raised") << "" << "" << "Max" << "50" << "" << "100";
QTest::newRow("leading zeros") << "" << "" << "Min" << "007" << "7" << "";
QTest::newRow("clear min") << "200" << "" << "Min" << "" << "" << "";
}
void TestXrayPagesUi::paddingBytesRange()
{
QFETCH(QString, minBefore);
QFETCH(QString, maxBefore);
QFETCH(QString, which);
QFETCH(QString, typed);
QFETCH(QString, minAfter);
QFETCH(QString, maxAfter);
XRAY_LOAD("PageProtocolXrayXPaddingBytesSettings");
m_model->setXPaddingBytesMin(minBefore);
m_model->setXPaddingBytesMax(maxBefore);
QQuickItem *row = minMaxRow(0);
clearAndType(minMaxInput(row, which), typed);
QCOMPARE(m_model->xPaddingBytesMin(), minAfter);
QCOMPARE(m_model->xPaddingBytesMax(), maxAfter);
QCOMPARE(minMaxInput(row, QStringLiteral("Max"))->property("text").toString(), maxAfter);
}
void TestXrayPagesUi::paddingBytesOverflowAndFilter()
{
XRAY_LOAD("PageProtocolXrayXPaddingBytesSettings");
QQuickItem *minInput = minMaxInput(minMaxRow(0), QStringLiteral("Min"));
clearAndType(minInput, QStringLiteral("9999999999"), false);
QCOMPARE(minInput->property("text").toString(), QStringLiteral("999999999"));
QCOMPARE(m_model->xPaddingBytesMin(), QStringLiteral("999999999"));
clearAndType(minInput, QStringLiteral("-12a"), false);
QCOMPARE(minInput->property("text").toString(), QStringLiteral("12"));
}
-384
View File
@@ -1,384 +0,0 @@
#include "testXrayPagesUi.h"
#include "ui/utils/pageEnum.h"
using namespace amnezia;
namespace
{
const QString kPortChangeText = QStringLiteral(
"All users with whom you shared a connection with will no longer be able to connect to it. "
"You will need to share the connection again.");
}
void TestXrayPagesUi::mainPortPlaceholderAndDefault()
{
XRAY_LOAD("PageProtocolXraySettings");
m_model->setPort(QString());
QQuickItem *port = field(QStringLiteral("Port"));
QVERIFY(port);
QCOMPARE(port->property("text").toString(), QString());
QCOMPARE(port->property("placeholderText").toString(), QStringLiteral("443"));
}
void TestXrayPagesUi::mainPortInput_data()
{
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
QTest::newRow("letters and symbols are blocked") << "a-+ .x1" << "1";
QTest::newRow("65536 cannot be typed") << "65536" << "6553";
QTest::newRow("70000 cannot be typed") << "70000" << "7000";
QTest::newRow("0 is clamped to 1") << "0" << "1";
QTest::newRow("0000 is clamped to 1") << "0000" << "1";
QTest::newRow("leading zeros dropped") << "0443" << "443";
QTest::newRow("65535 accepted") << "65535" << "65535";
}
void TestXrayPagesUi::mainPortInput()
{
QFETCH(QString, typed);
QFETCH(QString, expected);
XRAY_LOAD("PageProtocolXraySettings");
QQuickItem *port = field(QStringLiteral("Port"));
QVERIFY(port);
clearAndType(port, typed);
QCOMPARE(port->property("text").toString(), expected);
QCOMPARE(m_model->port(), expected);
}
void TestXrayPagesUi::mainPortEditTogglesSave()
{
XRAY_LOAD("PageProtocolXraySettings");
QQuickItem *port = field(QStringLiteral("Port"));
QQuickItem *save = saveButton();
QVERIFY(port && save);
QVERIFY(!save->isEnabled());
clearAndType(port, QStringLiteral("8443"), false);
QCOMPARE(m_model->port(), QStringLiteral("8443"));
QVERIFY(m_model->hasUnsavedChanges());
QVERIFY(save->isEnabled());
clearAndType(port, QStringLiteral("443"), false);
QVERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!save->isEnabled());
}
void TestXrayPagesUi::mainEmptyPortDisablesSave()
{
XRAY_LOAD("PageProtocolXraySettings");
m_model->setXhttpDisableGrpc(false);
QVERIFY(m_model->hasUnsavedChanges());
QQuickItem *port = field(QStringLiteral("Port"));
QQuickItem *save = saveButton();
QVERIFY(port && save);
QVERIFY(save->isEnabled());
clearAndType(port, QString(), false);
QCOMPARE(m_model->port(), QString());
QVERIFY2(!save->isEnabled(), "Save must stay disabled while the port is empty");
}
void TestXrayPagesUi::mainEmptyPortErrorOnFocusOut()
{
XRAY_LOAD("PageProtocolXraySettings");
QQuickItem *portField = typed("TextFieldType", "headerText", QStringLiteral("Port"));
QQuickItem *port = input(portField);
QVERIFY(port);
clearAndType(port, QString(), false);
port->setProperty("focus", false);
QTest::qWait(50);
QCOMPARE(portField->property("errorText").toString(), QStringLiteral("The field can't be empty"));
click(port);
type(QStringLiteral("1"));
QCOMPARE(portField->property("errorText").toString(), QString());
}
void TestXrayPagesUi::mainPortFollowsModelOnlyWhenUnfocused()
{
XRAY_LOAD("PageProtocolXraySettings");
QQuickItem *port = field(QStringLiteral("Port"));
QVERIFY(port);
m_model->setPort(QStringLiteral("2053"));
QCOMPARE(port->property("text").toString(), QStringLiteral("2053"));
click(port);
m_model->setPort(QStringLiteral("9999"));
QCOMPARE(port->property("text").toString(), QStringLiteral("2053"));
}
void TestXrayPagesUi::mainSaveRefusesInvalidPort()
{
XRAY_LOAD("PageProtocolXraySettings");
m_model->setPort(QStringLiteral("0"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
QQuickItem *save = saveButton();
QVERIFY(save && save->isEnabled());
click(save);
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(), QStringLiteral("Port must be in the range of 1 to 65535"));
}
void TestXrayPagesUi::mainSaveJoinsErrors()
{
XRAY_LOAD("PageProtocolXraySettings");
m_model->setPort(QStringLiteral("0"));
m_model->setSni(QStringLiteral("*"));
m_model->setXhttpHost(QStringLiteral("bad host"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
click(saveButton());
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(),
QStringLiteral("Port must be in the range of 1 to 65535\n"
"SNI: enter a valid IP address or domain name\n"
"Host: enter a valid IP address or domain name"));
}
void TestXrayPagesUi::mainPortChangeDialog()
{
XRAY_LOAD("PageProtocolXraySettings");
XRAY_QUESTIONS(questions);
clearAndType(field(QStringLiteral("Port")), QStringLiteral("8443"));
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Save settings?"));
QCOMPARE(questions.first().at(1).toString(), kPortChangeText);
QCOMPARE(questions.first().at(2).toString(), QStringLiteral("Continue"));
QCOMPARE(questions.first().at(3).toString(), QStringLiteral("Cancel"));
answer(questions, false);
}
void TestXrayPagesUi::mainTransportSubtitle_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("subtitle");
QTest::newRow("raw") << "raw" << "RAW (TCP)";
QTest::newRow("xhttp") << "xhttp" << "XHTTP";
QTest::newRow("mkcp") << "mkcp" << "mKCP";
}
void TestXrayPagesUi::mainTransportSubtitle()
{
QFETCH(QString, transport);
QFETCH(QString, subtitle);
XRAY_LOAD("PageProtocolXraySettings");
m_model->setTransport(transport);
QQuickItem *row = typed("ListItemType", "text", QStringLiteral("Transport"));
QVERIFY(row);
QCOMPARE(row->property("subtitle").toString(), subtitle);
}
void TestXrayPagesUi::mainSecuritySubtitle_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("subtitle");
QTest::newRow("raw + none") << "raw" << "none" << "None";
QTest::newRow("raw + tls") << "raw" << "tls" << "TLS";
QTest::newRow("raw + reality") << "raw" << "reality" << "Reality";
QTest::newRow("mkcp + tls") << "mkcp" << "tls" << "TLS";
QTest::newRow("mkcp + reality shows None") << "mkcp" << "reality" << "None";
}
void TestXrayPagesUi::mainSecuritySubtitle()
{
QFETCH(QString, transport);
QFETCH(QString, security);
QFETCH(QString, subtitle);
XRAY_LOAD("PageProtocolXraySettings");
m_model->setTransport(transport);
m_model->setSecurity(security);
QQuickItem *row = typed("ListItemType", "text", QStringLiteral("Security"));
QVERIFY(row);
QCOMPARE(row->property("subtitle").toString(), subtitle);
}
void TestXrayPagesUi::mainFlowSubtitle_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("flow");
QTest::addColumn<QString>("subtitle");
QTest::newRow("raw + tls + vision") << "raw" << "tls" << "xtls-rprx-vision" << "xtls-rprx-vision";
QTest::newRow("raw + reality + udp443") << "raw" << "reality" << "xtls-rprx-vision-udp443" << "xtls-rprx-vision-udp443";
QTest::newRow("raw + reality + empty") << "raw" << "reality" << "" << "Empty";
QTest::newRow("raw + none + vision") << "raw" << "none" << "xtls-rprx-vision" << "Empty";
QTest::newRow("xhttp + reality + vision") << "xhttp" << "reality" << "xtls-rprx-vision" << "Empty";
QTest::newRow("mkcp + tls + vision") << "mkcp" << "tls" << "xtls-rprx-vision" << "Empty";
}
void TestXrayPagesUi::mainFlowSubtitle()
{
QFETCH(QString, transport);
QFETCH(QString, security);
QFETCH(QString, flow);
QFETCH(QString, subtitle);
XRAY_LOAD("PageProtocolXraySettings");
m_model->setTransport(transport);
m_model->setSecurity(security);
m_model->setFlow(flow);
QQuickItem *row = typed("ListItemType", "text", QStringLiteral("Flow"));
QVERIFY(row);
QCOMPARE(row->property("subtitle").toString(), subtitle);
}
void TestXrayPagesUi::mainRowsNavigate_data()
{
QTest::addColumn<QString>("row");
QTest::addColumn<int>("page");
QTest::newRow("Transport") << "Transport" << static_cast<int>(PageLoader::PageEnum::PageProtocolXrayTransportSettings);
QTest::newRow("Security") << "Security" << static_cast<int>(PageLoader::PageEnum::PageProtocolXraySecuritySettings);
QTest::newRow("Flow") << "Flow" << static_cast<int>(PageLoader::PageEnum::PageProtocolXrayFlowSettings);
}
void TestXrayPagesUi::mainRowsNavigate()
{
QFETCH(QString, row);
QFETCH(int, page);
XRAY_LOAD("PageProtocolXraySettings");
QSignalSpy pages(m_pages, &PageController::goToPage);
click(typed("ListItemType", "text", row));
QCOMPARE(pages.count(), 1);
QCOMPARE(static_cast<int>(qvariant_cast<PageLoader::PageEnum>(pages.first().at(0))), page);
}
void TestXrayPagesUi::mainSnapshotsIconNavigates()
{
XRAY_LOAD("PageProtocolXraySettings");
QSignalSpy pages(m_pages, &PageController::goToPage);
QQuickItem *icon = iconButton(QStringLiteral("more-vertical.svg"));
QVERIFY(icon);
click(icon);
QCOMPARE(pages.count(), 1);
QCOMPARE(qvariant_cast<PageLoader::PageEnum>(pages.first().at(0)), PageLoader::PageEnum::PageProtocolXraySnapshots);
}
void TestXrayPagesUi::mainBackButtonClosesPage()
{
XRAY_LOAD("PageProtocolXraySettings");
QSignalSpy closed(m_pages, &PageController::closePage);
QQuickItem *back = iconButton(QStringLiteral("arrow-left.svg"));
QVERIFY(back);
click(back);
QCOMPARE(closed.count(), 1);
}
void TestXrayPagesUi::mainResetDialogTexts()
{
XRAY_LOAD("PageProtocolXraySettings");
XRAY_QUESTIONS(questions);
click(typed("ListItemType", "text", QStringLiteral("Reset settings")));
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Reset settings?"));
QCOMPARE(questions.first().at(1).toString(), QStringLiteral("All XRay settings will be restored to defaults."));
QCOMPARE(questions.first().at(2).toString(), QStringLiteral("Reset"));
QCOMPARE(questions.first().at(3).toString(), QStringLiteral("Cancel"));
}
void TestXrayPagesUi::mainResetAccepted()
{
XrayServerConfig srv = xraytest::baseConfig("xhttp", "tls", "");
srv.port = QStringLiteral("8443");
storeAndOpen(srv);
XRAY_LOAD("PageProtocolXraySettings");
XRAY_QUESTIONS(questions);
QSignalSpy notes(m_pages, &PageController::showNotificationMessage);
click(typed("ListItemType", "text", QStringLiteral("Reset settings")));
QCOMPARE(questions.count(), 1);
answer(questions, true);
QCOMPARE(notes.count(), 1);
QCOMPARE(notes.first().at(0).toString(),
QStringLiteral("Settings were reset to defaults. Tap Save to apply them on the server."));
QCOMPARE(m_model->transport(), QStringLiteral("raw"));
QCOMPARE(m_model->security(), QStringLiteral("reality"));
QCOMPARE(m_model->flow(), QStringLiteral("xtls-rprx-vision"));
QCOMPARE(m_model->port(), QStringLiteral("443"));
QCOMPARE(m_model->sni(), QStringLiteral("www.googletagmanager.com"));
QCOMPARE(typed("ListItemType", "text", QStringLiteral("Transport"))->property("subtitle").toString(),
QStringLiteral("RAW (TCP)"));
QCOMPARE(typed("ListItemType", "text", QStringLiteral("Security"))->property("subtitle").toString(),
QStringLiteral("Reality"));
QCOMPARE(typed("ListItemType", "text", QStringLiteral("Flow"))->property("subtitle").toString(),
QStringLiteral("xtls-rprx-vision"));
QCOMPARE(field(QStringLiteral("Port"))->property("text").toString(), QStringLiteral("443"));
QVERIFY(saveButton()->isEnabled());
}
void TestXrayPagesUi::mainResetCancelled()
{
XRAY_LOAD("PageProtocolXraySettings");
XRAY_QUESTIONS(questions);
QSignalSpy notes(m_pages, &PageController::showNotificationMessage);
click(typed("ListItemType", "text", QStringLiteral("Reset settings")));
QCOMPARE(questions.count(), 1);
answer(questions, false);
QCOMPARE(notes.count(), 0);
QCOMPARE(m_model->transport(), QStringLiteral("xhttp"));
QVERIFY(!m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::mainKeyMigrationDialog()
{
XRAY_LOAD("PageProtocolXraySettings");
XRAY_QUESTIONS(questions);
QSignalSpy busy(m_pages, &PageController::showBusyIndicator);
emit m_core->m_installUiController->xrayKeyMigrationNeedsConfirm(QStringLiteral("MSG"));
QTest::qWait(50);
QCOMPARE(busy.count(), 1);
QCOMPARE(busy.first().at(0).toBool(), false);
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Recreate XRay with new keys?"));
QCOMPARE(questions.first().at(1).toString(), QStringLiteral("MSG"));
QCOMPARE(questions.first().at(2).toString(), QStringLiteral("Continue"));
QCOMPARE(questions.first().at(3).toString(), QStringLiteral("Cancel"));
answer(questions, false);
}
void TestXrayPagesUi::mainReadOnlyServer()
{
selectServer(m_readOnlyServerId);
storeAndOpen(xraytest::baseConfig("xhttp", "reality", ""), m_readOnlyServerId);
XRAY_LOAD("PageProtocolXraySettings");
QSignalSpy pages(m_pages, &PageController::goToPage);
QQuickItem *note = typed("SmallTextType", "text",
QStringLiteral("You have read-only access to this server. XRay settings cannot be edited."));
QVERIFY(note && note->isVisible());
QVERIFY(!typed("ListItemType", "text", QStringLiteral("Reset settings"))->isVisible());
QVERIFY(!saveButton()->isVisible());
QVERIFY(!field(QStringLiteral("Port"))->isEnabled());
for (const QString &row : { QStringLiteral("Transport"), QStringLiteral("Security"), QStringLiteral("Flow") })
QVERIFY2(!typed("ListItemType", "text", row)->isEnabled(), qPrintable(row));
click(typed("ListItemType", "text", QStringLiteral("Transport")));
QCOMPARE(pages.count(), 0);
}
void TestXrayPagesUi::mainWritableServerBaseline()
{
XRAY_LOAD("PageProtocolXraySettings");
QQuickItem *note = typed("SmallTextType", "text",
QStringLiteral("You have read-only access to this server. XRay settings cannot be edited."));
QVERIFY(note && !note->isVisible());
QVERIFY(typed("ListItemType", "text", QStringLiteral("Reset settings"))->isVisible());
QQuickItem *save = saveButton();
QVERIFY(save->isVisible());
QVERIFY(!save->isEnabled());
}
-376
View File
@@ -1,376 +0,0 @@
#include "testXrayPagesUi.h"
using namespace amnezia;
namespace
{
const QString kSni = QStringLiteral("Server Name (SNI)");
const QString kSniError = QStringLiteral("Enter a valid IP address or domain name");
const QString kDeviceOnlyText =
QStringLiteral("The server will not be changed now. The new settings apply the next time you connect.");
const QString kServerBreaksSharedText = QStringLiteral(
"The server configuration will be updated. All users with whom you shared a connection with will no "
"longer be able to connect to it. You will need to share the connection again.");
const QStringList kFingerprints = { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" };
const QStringList kAlpn = { "h2", "http/1.1", "h2,http/1.1" };
}
void TestXrayPagesUi::securityMatrix_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("checked");
QTest::addColumn<bool>("realityEnabled");
QTest::addColumn<bool>("hint");
QTest::addColumn<bool>("alpn");
QTest::addColumn<bool>("tlsFingerprint");
QTest::addColumn<bool>("realityFingerprint");
QTest::addColumn<int>("sniFields");
for (const QString t : { QStringLiteral("raw"), QStringLiteral("xhttp") }) {
QTest::newRow(qPrintable(t + " + none")) << t << "none" << "None" << true << false << false << false << false << 0;
QTest::newRow(qPrintable(t + " + tls")) << t << "tls" << "TLS" << true << false << true << true << false << 1;
QTest::newRow(qPrintable(t + " + reality")) << t << "reality" << "Reality" << true << false << false << false << true << 1;
}
QTest::newRow("mkcp + none") << "mkcp" << "none" << "None" << false << true << false << false << false << 0;
QTest::newRow("mkcp + tls") << "mkcp" << "tls" << "TLS" << false << true << true << false << false << 1;
QTest::newRow("mkcp + reality shows None") << "mkcp" << "reality" << "None" << false << true << false << false << false << 0;
}
void TestXrayPagesUi::securityMatrix()
{
QFETCH(QString, transport);
QFETCH(QString, security);
QFETCH(QString, checked);
QFETCH(bool, realityEnabled);
QFETCH(bool, hint);
QFETCH(bool, alpn);
QFETCH(bool, tlsFingerprint);
QFETCH(bool, realityFingerprint);
QFETCH(int, sniFields);
openStored(xraytest::baseConfig(transport, security, ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
for (const QString &text : { QStringLiteral("None"), QStringLiteral("TLS"), QStringLiteral("Reality") }) {
QQuickItem *radio = typed("RadioItemType", "text", text);
QVERIFY(radio);
QCOMPARE(radio->property("checked").toBool(), text == checked);
}
QCOMPARE(typed("RadioItemType", "text", QStringLiteral("Reality"))->isEnabled(), realityEnabled);
QCOMPARE(typed("SmallTextType", "text",
QStringLiteral("REALITY is not supported with the mKCP transport. Use None or TLS."))->isVisible(),
hint);
QCOMPARE(typed("DropDownType", "descriptionText", QStringLiteral("ALPN"))->isVisible(), alpn);
QList<QQuickItem *> fingerprints;
for (QQuickItem *dd : allOfType("DropDownType")) {
if (dd->property("descriptionText").toString() == QLatin1String("Fingerprint"))
fingerprints << dd;
}
QCOMPARE(fingerprints.size(), 2);
QCOMPARE(fingerprints.at(0)->isVisible(), tlsFingerprint);
QCOMPARE(fingerprints.at(1)->isVisible(), realityFingerprint);
int visibleSni = 0;
for (QQuickItem *f : allOfType("TextFieldType")) {
if (f->property("headerText").toString() == kSni && f->isVisible())
++visibleSni;
}
QCOMPARE(visibleSni, sniFields);
}
void TestXrayPagesUi::securityRadioRoundTrip()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
click(typed("RadioItemType", "text", QStringLiteral("TLS")));
QCOMPARE(m_model->security(), QStringLiteral("tls"));
QVERIFY(typed("RadioItemType", "text", QStringLiteral("TLS"))->property("checked").toBool());
QVERIFY(!typed("RadioItemType", "text", QStringLiteral("Reality"))->property("checked").toBool());
QVERIFY(saveButton()->isEnabled());
click(typed("RadioItemType", "text", QStringLiteral("None")));
QCOMPARE(m_model->security(), QStringLiteral("none"));
click(typed("RadioItemType", "text", QStringLiteral("Reality")));
QCOMPARE(m_model->security(), QStringLiteral("reality"));
QVERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::securityRealityNotClickableOnMkcp()
{
openStored(xraytest::baseConfig("mkcp", "tls", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *reality = typed("RadioItemType", "text", QStringLiteral("Reality"));
QVERIFY(reality && !reality->isEnabled());
click(reality);
QCOMPARE(m_model->security(), QStringLiteral("tls"));
QVERIFY(!m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::securityMkcpStoredReality()
{
openStored(xraytest::baseConfig("mkcp", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QVERIFY(typed("RadioItemType", "text", QStringLiteral("None"))->property("checked").toBool());
QCOMPARE(m_model->security(), QStringLiteral("reality"));
QVERIFY(!m_model->hasUnsavedChanges());
click(typed("RadioItemType", "text", QStringLiteral("None")));
QCOMPARE(m_model->security(), QStringLiteral("none"));
QVERIFY(saveButton()->isEnabled());
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(1).toString(), kDeviceOnlyText);
answer(questions, false);
}
void TestXrayPagesUi::securityDropDownOptions_data()
{
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("description");
QTest::addColumn<QStringList>("expected");
QTest::addColumn<QString>("current");
QTest::newRow("tls ALPN") << "tls" << "ALPN" << kAlpn << "h2";
QTest::newRow("tls Fingerprint") << "tls" << "Fingerprint" << kFingerprints << "chrome";
QTest::newRow("reality Fingerprint") << "reality" << "Fingerprint" << kFingerprints << "chrome";
}
void TestXrayPagesUi::securityDropDownOptions()
{
QFETCH(QString, security);
QFETCH(QString, description);
QFETCH(QStringList, expected);
QFETCH(QString, current);
openStored(xraytest::baseConfig("raw", security, ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *dd = dropDown(description);
QVERIFY(dd && dd->isVisible());
QCOMPARE(optionNames(dd, false), expected);
QCOMPARE(optionNames(dd, true), QStringList { current });
}
void TestXrayPagesUi::securityDropDownRoundTrip_data()
{
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("description");
QTest::addColumn<QString>("option");
for (const QString &o : kAlpn)
QTest::newRow(qPrintable("ALPN = " + o)) << "tls" << "ALPN" << o;
for (const QString &o : kFingerprints)
QTest::newRow(qPrintable("tls Fingerprint = " + o)) << "tls" << "Fingerprint" << o;
for (const QString &o : kFingerprints)
QTest::newRow(qPrintable("reality Fingerprint = " + o)) << "reality" << "Fingerprint" << o;
}
void TestXrayPagesUi::securityDropDownRoundTrip()
{
QFETCH(QString, security);
QFETCH(QString, description);
QFETCH(QString, option);
openStored(xraytest::baseConfig("raw", security, ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *dd = dropDown(description);
QVERIFY(dd);
QVERIFY(chooseOption(dd, option));
QCOMPARE(description == QLatin1String("ALPN") ? m_model->alpn() : m_model->fingerprint(), option);
QCOMPARE(dd->property("text").toString(), option);
const bool isDefault = option == QLatin1String("h2") || option == QLatin1String("chrome");
QCOMPARE(saveButton()->isEnabled(), !isDefault);
QCOMPARE(optionNames(dd, true), QStringList { option });
}
void TestXrayPagesUi::securityFingerprintSharedBetweenBlocks()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QVERIFY(chooseOption(dropDown(QStringLiteral("Fingerprint")), QStringLiteral("safari")));
click(typed("RadioItemType", "text", QStringLiteral("TLS")));
QQuickItem *tlsFingerprint = dropDown(QStringLiteral("Fingerprint"));
QVERIFY(tlsFingerprint && tlsFingerprint->isVisible());
QCOMPARE(tlsFingerprint->property("text").toString(), QStringLiteral("safari"));
QCOMPARE(optionNames(tlsFingerprint, true), QStringList { QStringLiteral("safari") });
}
void TestXrayPagesUi::securityValidSni()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
QVERIFY(sni);
clearAndType(sni, QStringLiteral("vpn.example.org"));
QCOMPARE(m_model->sni(), QStringLiteral("vpn.example.org"));
QCOMPARE(m_model->site(), QStringLiteral("vpn.example.org"));
QCOMPARE(ownerField(sni)->property("errorText").toString(), QString());
QVERIFY(saveButton()->isEnabled());
}
void TestXrayPagesUi::securitySniSiteCoupling()
{
XrayServerConfig srv = xraytest::baseConfig("raw", "reality", "");
srv.sni = QStringLiteral("a.example.com");
srv.site = QStringLiteral("b.example.com");
openStored(srv);
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
QVERIFY(sni);
click(sni);
key(Qt::Key_Return);
QCOMPARE(m_model->site(), QStringLiteral("b.example.com"));
QVERIFY(!m_model->hasUnsavedChanges());
key(Qt::Key_End);
key(Qt::Key_Backspace);
type(QStringLiteral("m"));
QCOMPARE(m_model->sni(), QStringLiteral("a.example.com"));
QCOMPARE(m_model->site(), QStringLiteral("a.example.com"));
QVERIFY(m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::securityInvalidSni_data()
{
QTest::addColumn<QString>("value");
QTest::newRow("no tld: localhost") << "localhost";
QTest::newRow("no tld: example") << "example";
QTest::newRow("digits only") << "123";
QTest::newRow("bad ipv4") << "999.1.1.1";
QTest::newRow("short ipv4") << "1.2.3";
}
void TestXrayPagesUi::securityInvalidSni()
{
QFETCH(QString, value);
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
QQuickItem *sniField = ownerField(sni);
QVERIFY(sniField);
clearAndType(sni, value);
QCOMPARE(m_model->sni(), value);
QCOMPARE(sniField->property("errorText").toString(), kSniError);
type(QStringLiteral("x"));
QCOMPARE(sniField->property("errorText").toString(), QString());
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
click(saveButton());
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(), QStringLiteral("SNI: enter a valid IP address or domain name"));
}
void TestXrayPagesUi::securityIpv4Sni()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
clearAndType(visibleField(kSni), QStringLiteral("1.2.3.4"));
QCOMPARE(m_model->sni(), QStringLiteral("1.2.3.4"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::securitySniFilter()
{
openStored(xraytest::baseConfig("raw", "tls", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
clearAndType(sni, QStringLiteral("a b:c/d_e*"), false);
QCOMPARE(sni->property("text").toString(), QStringLiteral("abcd_e"));
QCOMPARE(m_model->sni(), QStringLiteral("abcd_e"));
}
void TestXrayPagesUi::securityEmptySni()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
clearAndType(sni, QString());
QCOMPARE(m_model->sni(), QString());
QCOMPARE(m_model->site(), QString());
QCOMPARE(sni->property("placeholderText").toString(), QStringLiteral("www.googletagmanager.com"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::securitySniErrorOnFocusOut()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(kSni);
QQuickItem *sniField = ownerField(sni);
QVERIFY(sniField);
clearAndType(sni, QStringLiteral("localhost"), false);
QCOMPARE(sniField->property("errorText").toString(), QString());
sni->setProperty("focus", false);
QTest::qWait(50);
QCOMPARE(sniField->property("errorText").toString(), kSniError);
}
void TestXrayPagesUi::securityNoneIgnoresSni()
{
openStored(xraytest::baseConfig("raw", "none", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
m_model->setSni(QStringLiteral("*"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::securitySaveDialogDescription_data()
{
QTest::addColumn<QString>("security");
QTest::addColumn<QString>("edit");
QTest::addColumn<QString>("expected");
QTest::newRow("fingerprint is device-only") << "reality" << "fingerprint" << kDeviceOnlyText;
QTest::newRow("alpn breaks shared configs") << "tls" << "alpn" << kServerBreaksSharedText;
QTest::newRow("reality to tls breaks shared configs") << "reality" << "security" << kServerBreaksSharedText;
QTest::newRow("sni breaks shared configs") << "reality" << "sni" << kServerBreaksSharedText;
}
void TestXrayPagesUi::securitySaveDialogDescription()
{
QFETCH(QString, security);
QFETCH(QString, edit);
QFETCH(QString, expected);
openStored(xraytest::baseConfig("raw", security, ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
if (edit == QLatin1String("fingerprint"))
QVERIFY(chooseOption(dropDown(QStringLiteral("Fingerprint")), QStringLiteral("firefox")));
else if (edit == QLatin1String("alpn"))
QVERIFY(chooseOption(dropDown(QStringLiteral("ALPN")), QStringLiteral("http/1.1")));
else if (edit == QLatin1String("security"))
click(typed("RadioItemType", "text", QStringLiteral("TLS")));
else if (edit == QLatin1String("sni"))
clearAndType(visibleField(kSni), QStringLiteral("www.example.org"));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Save settings?"));
QCOMPARE(questions.first().at(1).toString(), expected);
answer(questions, false);
}
-742
View File
@@ -1,742 +0,0 @@
#include "testXrayPagesUi.h"
#include "ui/utils/pageEnum.h"
using namespace amnezia;
namespace
{
const QString kDeviceOnlyText =
QStringLiteral("The server will not be changed now. The new settings apply the next time you connect.");
const QString kServerKeepsSharedText = QStringLiteral(
"The server configuration will be updated. If XRay on the server is outdated, the container will be "
"recreated with the current version; connections that were already shared keep working.");
const QString kServerBreaksSharedText = QStringLiteral(
"The server configuration will be updated. All users with whom you shared a connection with will no "
"longer be able to connect to it. You will need to share the connection again.");
QString xhttpValue(const XrayConfigModel *m, const QString &header)
{
if (header == QLatin1String("Mode"))
return m->xhttpMode();
if (header == QLatin1String("UplinkHTTPMethod"))
return m->xhttpUplinkMethod();
if (header == QLatin1String("SessionPlacement"))
return m->xhttpSessionPlacement();
if (header == QLatin1String("SeqPlacement"))
return m->xhttpSeqPlacement();
if (header == QLatin1String("UplinkDataPlacement"))
return m->xhttpUplinkDataPlacement();
if (header == QLatin1String("SessionKey"))
return m->xhttpSessionKey();
if (header == QLatin1String("SeqKey"))
return m->xhttpSeqKey();
if (header == QLatin1String("UplinkDataKey"))
return m->xhttpUplinkDataKey();
if (header == QLatin1String("UplinkChunkSize"))
return m->xhttpUplinkChunkSize();
if (header == QLatin1String("scMaxBufferedPosts"))
return m->xhttpScMaxBufferedPosts();
if (header == QLatin1String("TTI"))
return m->mkcpTti();
if (header == QLatin1String("uplinkCapacity"))
return m->mkcpUplinkCapacity();
if (header == QLatin1String("downlinkCapacity"))
return m->mkcpDownlinkCapacity();
if (header == QLatin1String("mtu"))
return m->mkcpMtu();
if (header == QLatin1String("cwndMultiplier"))
return m->mkcpCwndMultiplier();
if (header == QLatin1String("maxSendingWindow"))
return m->mkcpMaxSendingWindow();
return QStringLiteral("<unknown field %1>").arg(header);
}
XrayServerConfig packetUpTls()
{
XrayServerConfig srv = xraytest::baseConfig("xhttp", "tls", "");
srv.xhttp.mode = QStringLiteral("Packet-up");
return srv;
}
}
void TestXrayPagesUi::transportXhttpRadioSubtitle()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *radio = typed("RadioItemType", "text", QStringLiteral("XHTTP"));
QVERIFY(radio);
QCOMPARE(radio->property("subtitle").toString(), QStringLiteral("Advanced users"));
}
void TestXrayPagesUi::transportRadioCheckedFollowsModel_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("checkedText");
QTest::newRow("raw") << "raw" << "RAW (TCP)";
QTest::newRow("xhttp") << "xhttp" << "XHTTP";
QTest::newRow("mkcp") << "mkcp" << "mKCP";
}
void TestXrayPagesUi::transportRadioCheckedFollowsModel()
{
QFETCH(QString, transport);
QFETCH(QString, checkedText);
openStored(xraytest::baseConfig(transport, "tls", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
for (const QString &text : { QStringLiteral("RAW (TCP)"), QStringLiteral("XHTTP"), QStringLiteral("mKCP") }) {
QQuickItem *radio = typed("RadioItemType", "text", text);
QVERIFY(radio);
QCOMPARE(radio->property("checked").toBool(), text == checkedText);
}
}
void TestXrayPagesUi::transportRadioReturnKey()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *radio = typed("RadioItemType", "text", QStringLiteral("RAW (TCP)"));
QVERIFY(radio);
radio->forceActiveFocus();
key(Qt::Key_Return);
QCOMPARE(m_model->transport(), QStringLiteral("raw"));
}
void TestXrayPagesUi::transportSectionVisibility_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<bool>("mkcpVisible");
QTest::addColumn<bool>("xhttpVisible");
QTest::newRow("raw") << "raw" << false << false;
QTest::newRow("xhttp") << "xhttp" << false << true;
QTest::newRow("mkcp") << "mkcp" << true << false;
}
void TestXrayPagesUi::transportSectionVisibility()
{
QFETCH(QString, transport);
QFETCH(bool, mkcpVisible);
QFETCH(bool, xhttpVisible);
openStored(xraytest::baseConfig(transport, "tls", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
QCOMPARE(typed("TextFieldType", "headerText", QStringLiteral("TTI"))->isVisible(), mkcpVisible);
QCOMPARE(typed("SmallTextType", "text", QStringLiteral("mKCP Settings"))->isVisible(), mkcpVisible);
QCOMPARE(typed("DropDownType", "headerText", QStringLiteral("Mode"))->isVisible(), xhttpVisible);
}
void TestXrayPagesUi::transportRawToXhttpFillsDefaults()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
QCOMPARE(m_model->xhttpMode(), QString());
XRAY_LOAD("PageProtocolXrayTransportSettings");
click(typed("RadioItemType", "text", QStringLiteral("XHTTP")));
QCOMPARE(m_model->transport(), QStringLiteral("xhttp"));
QCOMPARE(m_model->xhttpMode(), QStringLiteral("Stream-one"));
QCOMPARE(m_model->xhttpUplinkMethod(), QStringLiteral("POST"));
QCOMPARE(m_model->xhttpSessionPlacement(), QStringLiteral("Path"));
QCOMPARE(m_model->xhttpSeqPlacement(), QStringLiteral("Path"));
QCOMPARE(m_model->xhttpUplinkDataPlacement(), QStringLiteral("Body"));
QCOMPARE(m_model->xhttpHost(), QStringLiteral("www.googletagmanager.com"));
QCOMPARE(m_model->xhttpScMaxEachPostBytesMin(), QStringLiteral("1000000"));
QCOMPARE(m_model->xhttpScMaxEachPostBytesMax(), QStringLiteral("1000000"));
QCOMPARE(m_model->xhttpScMinPostsIntervalMsMin(), QStringLiteral("100"));
QCOMPARE(m_model->xhttpScMinPostsIntervalMsMax(), QStringLiteral("800"));
QCOMPARE(m_model->xhttpScStreamUpServerSecsMin(), QStringLiteral("1"));
QCOMPARE(m_model->xhttpScStreamUpServerSecsMax(), QStringLiteral("100"));
QCOMPARE(m_model->xhttpPath(), QString());
QCOMPARE(typed("DropDownType", "headerText", QStringLiteral("Mode"))->property("text").toString(),
QStringLiteral("Stream-one"));
}
void TestXrayPagesUi::transportSwitchKeepsXhttpValues()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
m_model->setXhttpHost(QStringLiteral("a.com"));
click(typed("RadioItemType", "text", QStringLiteral("mKCP")));
QCOMPARE(m_model->transport(), QStringLiteral("mkcp"));
QCOMPARE(m_model->xhttpHost(), QStringLiteral("a.com"));
}
void TestXrayPagesUi::transportPacketUpFieldsMoreRows_data()
{
QTest::addColumn<QString>("mode");
QTest::addColumn<QString>("security");
QTest::addColumn<bool>("visible");
QTest::newRow("packet-up + reality") << "Packet-up" << "reality" << true;
QTest::newRow("auto + tls") << "Auto" << "tls" << true;
QTest::newRow("empty + none") << "" << "none" << true;
QTest::newRow("empty + reality") << "" << "reality" << false;
QTest::newRow("stream-up + none") << "Stream-up" << "none" << false;
QTest::newRow("stream-one + tls") << "Stream-one" << "tls" << false;
}
void TestXrayPagesUi::transportPacketUpFieldsMoreRows()
{
QFETCH(QString, mode);
QFETCH(QString, security);
QFETCH(bool, visible);
openStored(xraytest::baseConfig("xhttp", security, ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
m_model->setXhttpMode(mode);
for (const QString &h : { QStringLiteral("SeqPlacement"), QStringLiteral("UplinkDataPlacement") })
QCOMPARE(typed("DropDownType", "headerText", h)->isVisible(), visible);
for (const QString &h : { QStringLiteral("SeqKey"), QStringLiteral("UplinkDataKey") })
QCOMPARE(typed("TextFieldType", "headerText", h)->isVisible(), visible);
}
void TestXrayPagesUi::transportPacketUpFieldsFollowModeLive()
{
openStored(xraytest::baseConfig("xhttp", "tls", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *seqKey = typed("TextFieldType", "headerText", QStringLiteral("SeqKey"));
QQuickItem *mode = typed("DropDownType", "headerText", QStringLiteral("Mode"));
QVERIFY(seqKey && mode);
QVERIFY(!seqKey->isVisible());
QVERIFY(chooseOption(mode, QStringLiteral("Packet-up")));
QVERIFY(seqKey->isVisible());
QVERIFY(chooseOption(mode, QStringLiteral("Stream-up")));
QVERIFY(!seqKey->isVisible());
}
void TestXrayPagesUi::transportAlwaysVisibleXhttpControls_data()
{
QTest::addColumn<QString>("mode");
QTest::newRow("Auto") << "Auto";
QTest::newRow("Packet-up") << "Packet-up";
QTest::newRow("Stream-up") << "Stream-up";
QTest::newRow("Stream-one") << "Stream-one";
}
void TestXrayPagesUi::transportAlwaysVisibleXhttpControls()
{
QFETCH(QString, mode);
XrayServerConfig srv = xraytest::baseConfig("xhttp", "tls", "");
srv.xhttp.mode = mode;
openStored(srv);
XRAY_LOAD("PageProtocolXrayTransportSettings");
for (const QString &h : { QStringLiteral("SessionPlacement"), QStringLiteral("UplinkHTTPMethod") })
QVERIFY2(typed("DropDownType", "headerText", h)->isVisible(), qPrintable(h));
for (const QString &h : { QStringLiteral("SessionKey"), QStringLiteral("Host"), QStringLiteral("Path"),
QStringLiteral("UplinkChunkSize"), QStringLiteral("scMaxBufferedPosts") })
QVERIFY2(typed("TextFieldType", "headerText", h)->isVisible(), qPrintable(h));
for (const QString &d : { QStringLiteral("noGRPCHeader"), QStringLiteral("noSSEHeader") })
QVERIFY2(typed("SwitchRowType", "descriptionText", d)->isVisible(), qPrintable(d));
}
void TestXrayPagesUi::transportDropDownOptions_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QStringList>("expected");
QTest::newRow("Mode") << "Mode" << QStringList { "Auto", "Packet-up", "Stream-up", "Stream-one" };
QTest::newRow("UplinkHTTPMethod") << "UplinkHTTPMethod" << QStringList { "POST", "PUT", "PATCH" };
QTest::newRow("SessionPlacement") << "SessionPlacement" << QStringList { "Path", "Header", "Cookie", "Query", "None" };
QTest::newRow("SeqPlacement") << "SeqPlacement" << QStringList { "Path", "Header", "Cookie", "Query", "None" };
QTest::newRow("UplinkDataPlacement") << "UplinkDataPlacement" << QStringList { "Body", "Auto", "Header", "Cookie" };
}
void TestXrayPagesUi::transportDropDownOptions()
{
QFETCH(QString, header);
QFETCH(QStringList, expected);
openStored(packetUpTls());
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *dd = typed("DropDownType", "headerText", header);
QVERIFY(dd);
QCOMPARE(optionNames(dd, false), expected);
QCOMPARE(optionNames(dd, true), QStringList { xhttpValue(m_model, header) });
}
void TestXrayPagesUi::transportDropDownRoundTrip_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("option");
const QList<QPair<QString, QStringList>> lists = {
{ "Mode", { "Auto", "Packet-up", "Stream-up", "Stream-one" } },
{ "UplinkHTTPMethod", { "POST", "PUT", "PATCH" } },
{ "SessionPlacement", { "Path", "Header", "Cookie", "Query", "None" } },
{ "SeqPlacement", { "Path", "Header", "Cookie", "Query", "None" } },
{ "UplinkDataPlacement", { "Body", "Auto", "Header", "Cookie" } },
};
for (const auto &list : lists) {
for (const QString &option : list.second)
QTest::newRow(qPrintable(list.first + QStringLiteral(" = ") + option)) << list.first << option;
}
}
void TestXrayPagesUi::transportDropDownRoundTrip()
{
QFETCH(QString, header);
QFETCH(QString, option);
openStored(packetUpTls());
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *dd = typed("DropDownType", "headerText", header);
QVERIFY(dd);
QVERIFY(chooseOption(dd, option));
QCOMPARE(xhttpValue(m_model, header), option);
QCOMPARE(dd->property("text").toString(), option);
QCOMPARE(optionNames(dd, true), QStringList { option });
}
void TestXrayPagesUi::transportDropDownEscape()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *dd = typed("DropDownType", "headerText", QStringLiteral("Mode"));
QObject *popup = openDropDown(dd);
QVERIFY(popup);
QVERIFY(closeWithEscape(popup));
QCOMPARE(m_model->xhttpMode(), QStringLiteral("Stream-one"));
QVERIFY(!m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::transportChoosingCurrentModeIsNoChange()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QVERIFY(chooseOption(typed("DropDownType", "headerText", QStringLiteral("Mode")), QStringLiteral("Stream-one")));
QVERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::transportPacketUpRealityAllowed()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QVERIFY(chooseOption(typed("DropDownType", "headerText", QStringLiteral("Mode")), QStringLiteral("Packet-up")));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::transportSwitches_data()
{
QTest::addColumn<QString>("description");
QTest::newRow("noGRPCHeader") << "noGRPCHeader";
QTest::newRow("noSSEHeader") << "noSSEHeader";
}
void TestXrayPagesUi::transportSwitches()
{
QFETCH(QString, description);
XRAY_LOAD("PageProtocolXrayTransportSettings");
auto value = [this, &description] {
return description == QLatin1String("noGRPCHeader") ? m_model->xhttpDisableGrpc() : m_model->xhttpDisableSse();
};
QQuickItem *sw = switcherOf(typed("SwitchRowType", "descriptionText", description));
QVERIFY(sw);
QCOMPARE(value(), true);
QCOMPARE(sw->property("checked").toBool(), true);
click(sw);
QCOMPARE(value(), false);
QVERIFY(m_model->hasUnsavedChanges());
QVERIFY(saveButton()->isEnabled());
click(sw);
QCOMPARE(value(), true);
QVERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::transportSwitchLabelToggles()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *label = typed("BodyTextType", "text", QStringLiteral("Disable gRPC Header"));
QVERIFY(label);
click(label);
QCOMPARE(m_model->xhttpDisableGrpc(), false);
}
void TestXrayPagesUi::transportHostFilter()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *host = field(QStringLiteral("Host"));
QVERIFY(host);
clearAndType(host, QStringLiteral("a b/c*@!"), false);
QCOMPARE(host->property("text").toString(), QStringLiteral("abc"));
QCOMPARE(m_model->xhttpHost(), QStringLiteral("abc"));
}
void TestXrayPagesUi::transportHostValidity_data()
{
QTest::addColumn<QString>("value");
QTest::addColumn<bool>("valid");
QTest::newRow("domain") << "example.com" << true;
QTest::newRow("ipv4") << "1.2.3.4" << true;
QTest::newRow("ipv6") << "::1" << true;
QTest::newRow("digits only") << "12345" << false;
QTest::newRow("no tld") << "localhost" << false;
QTest::newRow("short ipv4") << "1.2.3" << false;
QTest::newRow("two hosts") << "a.com,b.com" << false;
QTest::newRow("host with port") << "example.com:443" << false;
}
void TestXrayPagesUi::transportHostValidity()
{
QFETCH(QString, value);
QFETCH(bool, valid);
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *hostField = typed("TextFieldType", "headerText", QStringLiteral("Host"));
clearAndType(input(hostField), value);
QCOMPARE(m_model->xhttpHost(), value);
QCOMPARE(hostField->property("errorText").toString(),
valid ? QString() : QStringLiteral("Enter a valid IP address or domain name"));
QCOMPARE(XrayConfigModel::isValidHost(value), valid);
if (!valid) {
type(QStringLiteral("x"));
QCOMPARE(hostField->property("errorText").toString(), QString());
}
}
void TestXrayPagesUi::transportEmptyHost()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *hostField = typed("TextFieldType", "headerText", QStringLiteral("Host"));
clearAndType(input(hostField), QString());
QCOMPARE(m_model->xhttpHost(), QString());
QCOMPARE(hostField->property("errorText").toString(), QString());
QCOMPARE(hostField->property("placeholderText").toString(), QStringLiteral("www.googletagmanager.com"));
}
void TestXrayPagesUi::transportSaveRefusesInvalidHost()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
m_model->setXhttpHost(QStringLiteral("12345"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
click(saveButton());
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(), QStringLiteral("Host: enter a valid IP address or domain name"));
}
void TestXrayPagesUi::transportPathFilter()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *path = field(QStringLiteral("Path"));
clearAndType(path, QStringLiteral("a b|c"), false);
QCOMPARE(path->property("text").toString(), QStringLiteral("abc"));
key(Qt::Key_Return);
QCOMPARE(m_model->xhttpPath(), QStringLiteral("/abc"));
}
void TestXrayPagesUi::transportPathEdgeCases_data()
{
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
QTest::newRow("empty stays empty") << "" << "";
QTest::newRow("slash kept") << "/x" << "/x";
QTest::newRow("nested gets slash") << "x/y" << "/x/y";
QTest::newRow("query kept") << "p?a=1" << "/p?a=1";
}
void TestXrayPagesUi::transportPathEdgeCases()
{
QFETCH(QString, typed);
QFETCH(QString, expected);
XRAY_LOAD("PageProtocolXrayTransportSettings");
clearAndType(field(QStringLiteral("Path")), typed);
QCOMPARE(m_model->xhttpPath(), expected);
}
void TestXrayPagesUi::transportKeyFieldsFilter_data()
{
QTest::addColumn<QString>("header");
QTest::newRow("SessionKey") << "SessionKey";
QTest::newRow("SeqKey") << "SeqKey";
QTest::newRow("UplinkDataKey") << "UplinkDataKey";
}
void TestXrayPagesUi::transportKeyFieldsFilter()
{
QFETCH(QString, header);
openStored(packetUpTls());
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *f = field(header);
QVERIFY(f);
clearAndType(f, QStringLiteral("a.b c/d=e_-"));
QCOMPARE(f->property("text").toString(), QStringLiteral("abcde_-"));
QCOMPARE(xhttpValue(m_model, header), QStringLiteral("abcde_-"));
}
void TestXrayPagesUi::transportNumberFields_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
for (const QString h : { QStringLiteral("UplinkChunkSize"), QStringLiteral("scMaxBufferedPosts") }) {
QTest::newRow(qPrintable(h + QStringLiteral(": above int32 is clamped"))) << h << "9999999999" << "2147483647";
QTest::newRow(qPrintable(h + QStringLiteral(": leading zeros"))) << h << "007" << "7";
QTest::newRow(qPrintable(h + QStringLiteral(": letters blocked"))) << h << "12a-3" << "123";
QTest::newRow(qPrintable(h + QStringLiteral(": zero"))) << h << "0" << "0";
QTest::newRow(qPrintable(h + QStringLiteral(": empty"))) << h << "" << "";
}
}
void TestXrayPagesUi::transportNumberFields()
{
QFETCH(QString, header);
QFETCH(QString, typed);
QFETCH(QString, expected);
XRAY_LOAD("PageProtocolXrayTransportSettings");
clearAndType(field(header), typed);
QCOMPARE(xhttpValue(m_model, header), expected);
}
void TestXrayPagesUi::transportMinMaxRows_data()
{
QTest::addColumn<int>("row");
QTest::addColumn<QString>("minBefore");
QTest::addColumn<QString>("maxBefore");
QTest::addColumn<QString>("which");
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("minAfter");
QTest::addColumn<QString>("maxAfter");
QTest::newRow("minPosts: min above max raises max") << 2 << "100" << "800" << "Min" << "900" << "900" << "900";
QTest::newRow("minPosts: max below min is raised") << 2 << "100" << "800" << "Max" << "50" << "100" << "100";
QTest::newRow("minPosts: empty min uses placeholder") << 2 << "" << "800" << "Max" << "50" << "" << "100";
QTest::newRow("minPosts: clear min") << 2 << "100" << "800" << "Min" << "" << "" << "800";
QTest::newRow("minPosts: leading zeros") << 2 << "100" << "800" << "Min" << "007" << "7" << "800";
QTest::newRow("streamUp: min above empty max placeholder") << 1 << "1" << "" << "Min" << "200" << "200" << "200";
QTest::newRow("eachPost: max below min") << 0 << "1000000" << "1000000" << "Max" << "5" << "1000000" << "1000000";
}
void TestXrayPagesUi::transportMinMaxRows()
{
QFETCH(int, row);
QFETCH(QString, minBefore);
QFETCH(QString, maxBefore);
QFETCH(QString, which);
QFETCH(QString, typed);
QFETCH(QString, minAfter);
QFETCH(QString, maxAfter);
XRAY_LOAD("PageProtocolXrayTransportSettings");
auto setRow = [this, row](const QString &mn, const QString &mx) {
if (row == 0) {
m_model->setXhttpScMaxEachPostBytesMin(mn);
m_model->setXhttpScMaxEachPostBytesMax(mx);
} else if (row == 1) {
m_model->setXhttpScStreamUpServerSecsMin(mn);
m_model->setXhttpScStreamUpServerSecsMax(mx);
} else {
m_model->setXhttpScMinPostsIntervalMsMin(mn);
m_model->setXhttpScMinPostsIntervalMsMax(mx);
}
};
auto rowValues = [this, row] {
if (row == 0)
return qMakePair(m_model->xhttpScMaxEachPostBytesMin(), m_model->xhttpScMaxEachPostBytesMax());
if (row == 1)
return qMakePair(m_model->xhttpScStreamUpServerSecsMin(), m_model->xhttpScStreamUpServerSecsMax());
return qMakePair(m_model->xhttpScMinPostsIntervalMsMin(), m_model->xhttpScMinPostsIntervalMsMax());
};
setRow(minBefore, maxBefore);
QQuickItem *rowItem = minMaxRow(row);
QVERIFY(rowItem);
clearAndType(minMaxInput(rowItem, which), typed);
QCOMPARE(rowValues().first, minAfter);
QCOMPARE(rowValues().second, maxAfter);
}
void TestXrayPagesUi::transportMinMaxOverflow()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *minInput = minMaxInput(minMaxRow(2), QStringLiteral("Min"));
QVERIFY(minInput);
clearAndType(minInput, QStringLiteral("9999999999"), false);
QCOMPARE(minInput->property("text").toString(), QStringLiteral("999999999"));
QCOMPARE(m_model->xhttpScMinPostsIntervalMsMin(), QStringLiteral("999999999"));
clearAndType(minInput, QStringLiteral("-12a"), false);
QCOMPARE(minInput->property("text").toString(), QStringLiteral("12"));
QVERIFY(typed("SmallTextType", "text", QStringLiteral("≤ 2147483647")));
}
void TestXrayPagesUi::transportMkcpPlaceholders_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("placeholder");
QTest::addColumn<QString>("helper");
QTest::newRow("TTI") << "TTI" << "50" << "Transmission time interval (ms). Valid range: 10–100.";
QTest::newRow("uplinkCapacity") << "uplinkCapacity" << "5" << "Uplink capacity (MB/s). Maximum: 2147483647.";
QTest::newRow("downlinkCapacity") << "downlinkCapacity" << "20" << "Downlink capacity (MB/s). Maximum: 2147483647.";
QTest::newRow("mtu") << "mtu" << "1350" << "Maximum packet size in bytes. Range: 576–1460.";
QTest::newRow("cwndMultiplier") << "cwndMultiplier" << "1" << "Congestion window multiplier. Minimum: 1.";
QTest::newRow("maxSendingWindow") << "maxSendingWindow" << ""
<< "Send window in bytes. Must not be smaller than mtu. Leave empty to let Xray decide.";
}
void TestXrayPagesUi::transportMkcpPlaceholders()
{
QFETCH(QString, header);
QFETCH(QString, placeholder);
QFETCH(QString, helper);
openStored(xraytest::baseConfig("mkcp", "none", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
QQuickItem *f = typed("TextFieldType", "headerText", header);
QVERIFY(f && f->isVisible());
QCOMPARE(f->property("placeholderText").toString(), placeholder);
QCOMPARE(f->property("helperText").toString(), helper);
}
void TestXrayPagesUi::transportMkcpClamps_data()
{
QTest::addColumn<QString>("header");
QTest::addColumn<QString>("mtuBefore");
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
QTest::newRow("TTI 101 cannot be typed") << "TTI" << "" << "101" << "10";
QTest::newRow("TTI 100") << "TTI" << "" << "100" << "100";
QTest::newRow("TTI 5 -> 10") << "TTI" << "" << "5" << "10";
QTest::newRow("TTI 0 -> 10") << "TTI" << "" << "0" << "10";
QTest::newRow("TTI 05 -> 10") << "TTI" << "" << "05" << "10";
QTest::newRow("TTI empty") << "TTI" << "" << "" << "";
QTest::newRow("uplink above int32") << "uplinkCapacity" << "" << "9999999999" << "2147483647";
QTest::newRow("uplink 0") << "uplinkCapacity" << "" << "0" << "0";
QTest::newRow("downlink above int32") << "downlinkCapacity" << "" << "9999999999" << "2147483647";
QTest::newRow("mtu 100 -> 576") << "mtu" << "" << "100" << "576";
QTest::newRow("mtu 9999 -> 1460") << "mtu" << "" << "9999" << "1460";
QTest::newRow("cwnd 0 -> 1") << "cwndMultiplier" << "" << "0" << "1";
QTest::newRow("window below mtu 1000") << "maxSendingWindow" << "1000" << "800" << "1000";
QTest::newRow("window below default mtu") << "maxSendingWindow" << "" << "800" << "1350";
QTest::newRow("window empty") << "maxSendingWindow" << "" << "" << "";
}
void TestXrayPagesUi::transportMkcpClamps()
{
QFETCH(QString, header);
QFETCH(QString, mtuBefore);
QFETCH(QString, typed);
QFETCH(QString, expected);
openStored(xraytest::baseConfig("mkcp", "none", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
m_model->setMkcpMtu(mtuBefore);
clearAndType(field(header), typed);
QCOMPARE(xhttpValue(m_model, header), expected);
}
void TestXrayPagesUi::transportMtuRaisesSendingWindow_data()
{
QTest::addColumn<QString>("windowBefore");
QTest::addColumn<QString>("mtuTyped");
QTest::addColumn<QString>("windowAfter");
QTest::newRow("600 raised to mtu 1000") << "600" << "1000" << "1000";
QTest::newRow("2000 above mtu kept") << "2000" << "1460" << "2000";
QTest::newRow("cleared mtu uses 1350") << "1000" << "" << "1350";
QTest::newRow("empty window stays empty") << "" << "1000" << "";
}
void TestXrayPagesUi::transportMtuRaisesSendingWindow()
{
QFETCH(QString, windowBefore);
QFETCH(QString, mtuTyped);
QFETCH(QString, windowAfter);
openStored(xraytest::baseConfig("mkcp", "none", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
m_model->setMkcpMaxSendingWindow(windowBefore);
clearAndType(field(QStringLiteral("mtu")), mtuTyped);
QCOMPARE(m_model->mkcpMaxSendingWindow(), windowAfter);
}
void TestXrayPagesUi::transportSaveDialogDescription_data()
{
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("edit");
QTest::addColumn<QString>("expected");
QTest::newRow("uplink method is device-only") << "xhttp" << "uplinkMethod" << kDeviceOnlyText;
QTest::newRow("chunk size is device-only") << "xhttp" << "chunk" << kDeviceOnlyText;
QTest::newRow("min posts interval is device-only") << "xhttp" << "minPosts" << kDeviceOnlyText;
QTest::newRow("buffered posts keep shared configs") << "xhttp" << "bufferedPosts" << kServerKeepsSharedText;
QTest::newRow("stream-up secs keep shared configs") << "xhttp" << "streamUp" << kServerKeepsSharedText;
QTest::newRow("mkcp tti keeps shared configs") << "mkcp" << "tti" << kServerKeepsSharedText;
QTest::newRow("path breaks shared configs") << "xhttp" << "path" << kServerBreaksSharedText;
QTest::newRow("host breaks shared configs") << "xhttp" << "host" << kServerBreaksSharedText;
QTest::newRow("noGRPC breaks shared configs") << "xhttp" << "grpc" << kServerBreaksSharedText;
QTest::newRow("transport breaks shared configs") << "xhttp" << "transport" << kServerBreaksSharedText;
}
void TestXrayPagesUi::transportSaveDialogDescription()
{
QFETCH(QString, transport);
QFETCH(QString, edit);
QFETCH(QString, expected);
openStored(xraytest::baseConfig(transport, "tls", ""));
XRAY_LOAD("PageProtocolXrayTransportSettings");
if (edit == QLatin1String("uplinkMethod"))
m_model->setXhttpUplinkMethod(QStringLiteral("PUT"));
else if (edit == QLatin1String("chunk"))
m_model->setXhttpUplinkChunkSize(QStringLiteral("4096"));
else if (edit == QLatin1String("minPosts"))
m_model->setXhttpScMinPostsIntervalMsMin(QStringLiteral("5"));
else if (edit == QLatin1String("bufferedPosts"))
m_model->setXhttpScMaxBufferedPosts(QStringLiteral("5"));
else if (edit == QLatin1String("streamUp"))
m_model->setXhttpScStreamUpServerSecsMax(QStringLiteral("50"));
else if (edit == QLatin1String("tti"))
m_model->setMkcpTti(QStringLiteral("20"));
else if (edit == QLatin1String("path"))
m_model->setXhttpPath(QStringLiteral("/other"));
else if (edit == QLatin1String("host"))
m_model->setXhttpHost(QStringLiteral("cdn.example.com"));
else if (edit == QLatin1String("grpc"))
m_model->setXhttpDisableGrpc(false);
else if (edit == QLatin1String("transport"))
click(typed("RadioItemType", "text", QStringLiteral("RAW (TCP)")));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Save settings?"));
QCOMPARE(questions.first().at(1).toString(), expected);
answer(questions, false);
}
void TestXrayPagesUi::transportSubpageRows()
{
XRAY_LOAD("PageProtocolXrayTransportSettings");
QSignalSpy pages(m_pages, &PageController::goToPage);
click(typed("ListItemType", "text", QStringLiteral("xPadding")));
QCOMPARE(pages.count(), 1);
QCOMPARE(qvariant_cast<PageLoader::PageEnum>(pages.at(0).at(0)), PageLoader::PageEnum::PageProtocolXrayXPaddingSettings);
QQuickItem *xmuxRow = typed("ListItemType", "text", QStringLiteral("XMux"));
QCOMPARE(xmuxRow->property("subtitle").toString(), QStringLiteral("On"));
m_model->setXmuxEnabled(false);
QCOMPARE(xmuxRow->property("subtitle").toString(), QStringLiteral("Off"));
click(xmuxRow);
QCOMPARE(pages.count(), 2);
QCOMPARE(qvariant_cast<PageLoader::PageEnum>(pages.at(1).at(0)), PageLoader::PageEnum::PageProtocolXrayXmuxSettings);
}
void TestXrayPagesUi::transportReadOnly()
{
selectServer(m_readOnlyServerId);
storeAndOpen(xraytest::baseConfig("xhttp", "reality", ""), m_readOnlyServerId);
XRAY_LOAD("PageProtocolXrayTransportSettings");
QVERIFY(!saveButton()->isVisible());
for (const QString &text : { QStringLiteral("RAW (TCP)"), QStringLiteral("XHTTP"), QStringLiteral("mKCP") })
QVERIFY2(!typed("RadioItemType", "text", text)->isEnabled(), qPrintable(text));
click(typed("RadioItemType", "text", QStringLiteral("RAW (TCP)")));
QCOMPARE(m_model->transport(), QStringLiteral("xhttp"));
}
-159
View File
@@ -1,159 +0,0 @@
#include "testXrayPagesUi.h"
using namespace amnezia;
namespace
{
const QString kXmuxRule = QStringLiteral("Set either maxConcurrency or maxConnections, not both");
const QString kXmuxError = QStringLiteral("XMux: set either maxConcurrency or maxConnections, not both");
const QString kDeviceOnlyText =
QStringLiteral("The server will not be changed now. The new settings apply the next time you connect.");
}
void TestXrayPagesUi::xmuxInitialState()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
QQuickItem *row = typed("SwitchRowType", "descriptionText", kXmuxRule);
QVERIFY(row);
QVERIFY(switcherOf(row)->property("checked").toBool());
QVERIFY(typed("SmallTextType", "text", kXmuxRule)->isVisible());
const QList<QQuickItem *> rows = allOfType("MinMaxRowType");
QCOMPARE(rows.size(), 5);
for (QQuickItem *r : rows) {
for (const QString which : { QStringLiteral("Min"), QStringLiteral("Max") }) {
QQuickItem *in = minMaxInput(r, which);
QVERIFY(in);
QCOMPARE(in->property("text").toString(), QStringLiteral("0"));
QCOMPARE(in->property("placeholderText").toString(), QStringLiteral("0"));
}
}
QQuickItem *keepAlive = typed("TextFieldType", "headerText", QStringLiteral("hKeepAlivePeriod"));
QCOMPARE(input(keepAlive)->property("text").toString(), QString());
QCOMPARE(keepAlive->property("helperText").toString(), QStringLiteral("HTTP keep-alive period. Integer, may be negative."));
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::xmuxSwitchDisablesFields()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
QQuickItem *sw = switcherOf(typed("SwitchRowType", "descriptionText", kXmuxRule));
QVERIFY(sw);
click(sw);
QCOMPARE(m_model->xmuxEnabled(), false);
for (QQuickItem *r : allOfType("MinMaxRowType")) {
QVERIFY(!minMaxInput(r, QStringLiteral("Min"))->isEnabled());
QVERIFY(!minMaxInput(r, QStringLiteral("Max"))->isEnabled());
}
QVERIFY(!field(QStringLiteral("hKeepAlivePeriod"))->isEnabled());
QVERIFY(saveButton()->isEnabled());
click(sw);
QCOMPARE(m_model->xmuxEnabled(), true);
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::xmuxMinRaisesMax()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(minMaxInput(minMaxRow(0), QStringLiteral("Min")), QStringLiteral("16"));
QCOMPARE(m_model->xmuxMaxConcurrencyMin(), QStringLiteral("16"));
QCOMPARE(m_model->xmuxMaxConcurrencyMax(), QStringLiteral("16"));
}
void TestXrayPagesUi::xmuxMaxBelowMin()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
m_model->setXmuxMaxConnectionsMin(QStringLiteral("4"));
m_model->setXmuxMaxConnectionsMax(QStringLiteral("4"));
clearAndType(minMaxInput(minMaxRow(1), QStringLiteral("Max")), QStringLiteral("2"));
QCOMPARE(m_model->xmuxMaxConnectionsMax(), QStringLiteral("4"));
}
void TestXrayPagesUi::xmuxBothLimitsRefusedOnSave()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(minMaxInput(minMaxRow(0), QStringLiteral("Min")), QStringLiteral("16"));
clearAndType(minMaxInput(minMaxRow(1), QStringLiteral("Min")), QStringLiteral("4"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
click(saveButton());
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(), kXmuxError);
}
void TestXrayPagesUi::xmuxZeroCountsAsUnset()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(minMaxInput(minMaxRow(0), QStringLiteral("Min")), QStringLiteral("16"));
QCOMPARE(m_model->xmuxMaxConnectionsMin(), QStringLiteral("0"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::xmuxDisabledIsNotValidated()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
m_model->setXmuxMaxConcurrencyMin(QStringLiteral("1"));
m_model->setXmuxMaxConnectionsMin(QStringLiteral("1"));
click(switcherOf(typed("SwitchRowType", "descriptionText", kXmuxRule)));
QCOMPARE(m_model->xmuxEnabled(), false);
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 1);
answer(questions, false);
}
void TestXrayPagesUi::xmuxTwoErrorsAtOnce()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
m_model->setXmuxMaxConcurrencyMin(QStringLiteral("1"));
m_model->setXmuxMaxConnectionsMin(QStringLiteral("1"));
m_model->setSni(QStringLiteral("*"));
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
click(saveButton());
QCOMPARE(errors.count(), 1);
QCOMPARE(errors.first().first().toString(),
QStringLiteral("SNI: enter a valid IP address or domain name\n") + kXmuxError);
}
void TestXrayPagesUi::xmuxKeepAlive_data()
{
QTest::addColumn<QString>("typed");
QTest::addColumn<QString>("expected");
QTest::newRow("lone minus") << "-" << "";
QTest::newRow("negative") << "-30" << "-30";
QTest::newRow("leading zeros") << "007" << "7";
QTest::newRow("minus zero") << "-0" << "0";
QTest::newRow("above int32") << "99999999999" << "2147483647";
QTest::newRow("below int32") << "-99999999999" << "-2147483648";
QTest::newRow("inner minus blocked") << "12-3" << "123";
QTest::newRow("empty") << "" << "";
}
void TestXrayPagesUi::xmuxKeepAlive()
{
QFETCH(QString, typed);
QFETCH(QString, expected);
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(field(QStringLiteral("hKeepAlivePeriod")), typed);
QCOMPARE(m_model->xmuxHKeepAlivePeriod(), expected);
}
void TestXrayPagesUi::xmuxEditIsDeviceOnly()
{
XRAY_LOAD("PageProtocolXrayXmuxSettings");
clearAndType(minMaxInput(minMaxRow(2), QStringLiteral("Min")), QStringLiteral("5"));
QCOMPARE(m_model->xmuxCMaxReuseTimesMin(), QStringLiteral("5"));
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(1).toString(), kDeviceOnlyText);
answer(questions, false);
}
@@ -1,341 +0,0 @@
#include <QJsonDocument>
#include <QRegularExpression>
#include "core/utils/errorCodes.h"
#include "testXrayPagesUi.h"
#include "ui/utils/pageEnum.h"
using namespace amnezia;
namespace
{
const QString kDeviceOnlyText =
QStringLiteral("The server will not be changed now. The new settings apply the next time you connect.");
const QStringList kAllPages = { "PageProtocolXraySettings", "PageProtocolXrayTransportSettings",
"PageProtocolXraySecuritySettings", "PageProtocolXrayFlowSettings",
"PageProtocolXrayXPaddingSettings", "PageProtocolXrayXPaddingBytesSettings",
"PageProtocolXrayXmuxSettings" };
}
void TestXrayPagesUi::saveInitiallyDisabled_data()
{
QTest::addColumn<QString>("page");
for (const QString &p : kAllPages)
QTest::newRow(qPrintable(p)) << p;
}
void TestXrayPagesUi::saveInitiallyDisabled()
{
QFETCH(QString, page);
loadPage(page);
if (QTest::currentTestFailed())
return;
QQuickItem *save = saveButton();
QVERIFY(save);
QVERIFY(save->isVisible());
QVERIFY(!save->isEnabled());
QSignalSpy errors(m_pages, qOverload<const QString &>(&PageController::showErrorMessage));
XRAY_QUESTIONS(questions);
click(save);
QCOMPARE(errors.count(), 0);
QCOMPARE(questions.count(), 0);
}
void TestXrayPagesUi::saveEditThenRevert()
{
openStored(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QSignalSpy unsaved(m_model, &XrayConfigModel::hasUnsavedChangesChanged);
QQuickItem *fingerprint = dropDown(QStringLiteral("Fingerprint"));
QVERIFY(chooseOption(fingerprint, QStringLiteral("firefox")));
QVERIFY(saveButton()->isEnabled());
QVERIFY(chooseOption(fingerprint, QStringLiteral("chrome")));
QVERIFY(!saveButton()->isEnabled());
QCOMPARE(unsaved.count(), 2);
}
void TestXrayPagesUi::saveReloadDiscardsEdits()
{
storeAndOpen(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
QQuickItem *sni = visibleField(QStringLiteral("Server Name (SNI)"));
clearAndType(sni, QStringLiteral("vpn.example.org"));
QVERIFY(m_model->hasUnsavedChanges());
m_core->m_installUiController->openServerSettings(m_serverId, static_cast<int>(DockerContainer::Xray),
static_cast<int>(Proto::Xray));
QVERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!saveButton()->isEnabled());
QCOMPARE(m_model->sni(), QStringLiteral("www.example.com"));
QCOMPARE(sni->property("text").toString(), QStringLiteral("www.example.com"));
}
void TestXrayPagesUi::saveCancelDoesNothing()
{
storeAndOpen(xraytest::baseConfig("raw", "reality", ""));
XRAY_LOAD("PageProtocolXraySecuritySettings");
m_model->setFingerprint(QStringLiteral("firefox"));
QSignalSpy pages(m_pages, &PageController::goToPage);
QSignalSpy finished(m_core->m_installUiController, &InstallUiController::updateContainerFinished);
QSignalSpy failed(m_core->m_installUiController, &InstallUiController::installationErrorOccurred);
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
answer(questions, false);
QTest::qWait(300);
QCOMPARE(pages.count(), 0);
QCOMPARE(finished.count(), 0);
QCOMPARE(failed.count(), 0);
QVERIFY(m_model->hasUnsavedChanges());
QVERIFY(saveButton()->isEnabled());
QCOMPARE(stored().fingerprint, QStringLiteral("chrome"));
}
void TestXrayPagesUi::saveDeviceOnlyContinue_data()
{
QTest::addColumn<QString>("page");
QTest::addColumn<QString>("transport");
QTest::addColumn<QString>("flow");
QTest::addColumn<QString>("edit");
QTest::newRow("security: fingerprint") << "PageProtocolXraySecuritySettings" << "raw" << "" << "fingerprint";
QTest::newRow("flow: vision -> udp443") << "PageProtocolXrayFlowSettings" << "raw" << "xtls-rprx-vision" << "udp443";
QTest::newRow("xmux: cMaxReuseTimes") << "PageProtocolXrayXmuxSettings" << "xhttp" << "" << "xmux";
QTest::newRow("transport: uplink method") << "PageProtocolXrayTransportSettings" << "xhttp" << "" << "method";
}
void TestXrayPagesUi::saveDeviceOnlyContinue()
{
QFETCH(QString, page);
QFETCH(QString, transport);
QFETCH(QString, flow);
QFETCH(QString, edit);
storeAndOpen(xraytest::baseConfig(transport, "reality", flow));
loadPage(page);
if (QTest::currentTestFailed())
return;
if (edit == QLatin1String("fingerprint"))
m_model->setFingerprint(QStringLiteral("firefox"));
else if (edit == QLatin1String("udp443"))
m_model->setFlow(QStringLiteral("xtls-rprx-vision-udp443"));
else if (edit == QLatin1String("xmux"))
m_model->setXmuxCMaxReuseTimesMin(QStringLiteral("5"));
else
m_model->setXhttpUplinkMethod(QStringLiteral("PUT"));
QVERIFY(!m_model->pendingChangeTouchesServer(QString()));
QSignalSpy pages(m_pages, &PageController::goToPage);
QSignalSpy finished(m_core->m_installUiController, &InstallUiController::updateContainerFinished);
QSignalSpy failed(m_core->m_installUiController, &InstallUiController::installationErrorOccurred);
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(1).toString(), kDeviceOnlyText);
answer(questions, true);
QTRY_COMPARE(finished.count() + failed.count(), 1);
QCOMPARE(failed.count(), 0);
QCOMPARE(finished.first().at(0).toString(), QStringLiteral("Settings updated successfully"));
QCOMPARE(finished.first().at(1).toBool(), false);
QCOMPARE(pages.count(), 0);
const XrayServerConfig after = stored();
if (edit == QLatin1String("fingerprint"))
QCOMPARE(after.fingerprint, QStringLiteral("firefox"));
else if (edit == QLatin1String("udp443"))
QCOMPARE(after.flow, QStringLiteral("xtls-rprx-vision-udp443"));
else if (edit == QLatin1String("xmux"))
QCOMPARE(after.xhttp.xmux.cMaxReuseTimesMin, QStringLiteral("5"));
else
QCOMPARE(after.xhttp.uplinkMethod, QStringLiteral("PUT"));
QTRY_VERIFY(!m_model->hasUnsavedChanges());
QVERIFY(!saveButton()->isEnabled());
}
void TestXrayPagesUi::saveServerChangeOnHostlessServer()
{
selectServer(m_hostlessServerId);
storeAndOpen(xraytest::baseConfig("raw", "reality", ""), m_hostlessServerId);
XRAY_LOAD("PageProtocolXraySecuritySettings");
m_model->setSni(QStringLiteral("www.example.org"));
QVERIFY(m_model->pendingChangeTouchesServer(QString()));
QSignalSpy pages(m_pages, &PageController::goToPage);
QSignalSpy finished(m_core->m_installUiController, &InstallUiController::updateContainerFinished);
QSignalSpy failed(m_core->m_installUiController, &InstallUiController::installationErrorOccurred);
XRAY_QUESTIONS(questions);
click(saveButton());
QCOMPARE(questions.count(), 1);
answer(questions, true);
QCOMPARE(pages.count(), 1);
QCOMPARE(qvariant_cast<PageLoader::PageEnum>(pages.first().at(0)), PageLoader::PageEnum::PageSetupWizardInstalling);
QTRY_COMPARE(finished.count() + failed.count(), 1);
QCOMPARE(failed.count(), 1);
QCOMPARE(qvariant_cast<ErrorCode>(failed.first().at(0)), ErrorCode::InternalError);
QCOMPARE(stored(m_hostlessServerId).sni, QStringLiteral("www.example.com"));
QVERIFY(m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::saveCppGuardRefusesInvalid()
{
m_model->setSni(QStringLiteral("*"));
QSignalSpy failed(m_core->m_installUiController, &InstallUiController::installationErrorOccurred);
m_core->m_installUiController->updateServerConfig(m_serverId, static_cast<int>(DockerContainer::Xray),
static_cast<int>(Proto::Xray), false);
QCOMPARE(failed.count(), 1);
QCOMPARE(qvariant_cast<ErrorCode>(failed.first().at(0)), ErrorCode::XrayServerConfigInvalid);
QCOMPARE(stored().sni, QStringLiteral("www.example.com"));
}
void TestXrayPagesUi::saveReadOnlySubpages_data()
{
QTest::addColumn<QString>("page");
for (const QString &p : kAllPages.mid(1))
QTest::newRow(qPrintable(p)) << p;
}
void TestXrayPagesUi::saveReadOnlySubpages()
{
QFETCH(QString, page);
selectServer(m_readOnlyServerId);
storeAndOpen(xraytest::baseConfig("xhttp", "reality", ""), m_readOnlyServerId);
loadPage(page);
if (QTest::currentTestFailed())
return;
QVERIFY(!saveButton()->isVisible());
int controls = 0;
for (const char *type : { "RadioItemType", "TextFieldType", "DropDownType", "SwitchRowType", "MinMaxRowType" }) {
for (QQuickItem *control : allOfType(type)) {
++controls;
QVERIFY2(!control->isEnabled(), qPrintable(QString::fromLatin1(type)));
}
}
QVERIFY(controls > 0);
}
void TestXrayPagesUi::snapshotsEmptyState()
{
XRAY_LOAD("PageProtocolXraySnapshots");
QCOMPARE(m_snapshots->rowCount(), 0);
QQuickItem *empty = typed("BodyTextType", "text",
QStringLiteral("No saved configurations yet.\nCreate one from the current settings."));
QVERIFY(empty && empty->isVisible());
QVERIFY(!typed("SmallTextType", "text", QStringLiteral("Configurations"))->isVisible());
}
void TestXrayPagesUi::snapshotsCreate()
{
XRAY_LOAD("PageProtocolXraySnapshots");
click(typed("ListItemType", "text", QStringLiteral("Create configuration based on current settings")));
QCOMPARE(m_snapshots->rowCount(), 1);
QQuickItem *row = nullptr;
QTRY_VERIFY((row = typed("ListItemType", "text", QStringLiteral("XHTTP Reality"))) != nullptr);
QVERIFY(QRegularExpression(QStringLiteral(R"(^\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}$)"))
.match(row->property("subtitle").toString())
.hasMatch());
QVERIFY(typed("SmallTextType", "text", QStringLiteral("Configurations"))->isVisible());
QVERIFY(!m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::snapshotsApply()
{
storeAndOpen(xraytest::baseConfig("raw", "tls", ""));
m_snapshots->createFromCurrentModel();
openStored(xraytest::baseConfig("xhttp", "reality", ""));
XRAY_LOAD("PageProtocolXraySnapshots");
QQuickItem *row = typed("ListItemType", "text", QStringLiteral("RAW (TCP) TLS"));
QVERIFY(row);
QObject *drawer = popupOf(m_page);
QVERIFY(drawer);
click(row);
QVERIFY(QTest::qWaitFor([drawer] { return drawer->property("opened").toBool(); }, 3000));
QTest::qWait(250);
QSignalSpy closed(m_pages, &PageController::closePage);
click(typed("ListItemType", "text", QStringLiteral("Apply configuration"), popupContent(drawer)));
QCOMPARE(closed.count(), 1);
QCOMPARE(m_model->transport(), QStringLiteral("raw"));
QCOMPARE(m_model->security(), QStringLiteral("tls"));
QVERIFY(m_model->hasUnsavedChanges());
}
void TestXrayPagesUi::snapshotsDelete()
{
m_snapshots->createFromCurrentModel();
XRAY_LOAD("PageProtocolXraySnapshots");
QSignalSpy removed(m_snapshots, &XrayConfigSnapshotsModel::configRemoved);
QObject *drawer = popupOf(m_page);
click(typed("ListItemType", "text", QStringLiteral("XHTTP Reality")));
QVERIFY(QTest::qWaitFor([drawer] { return drawer->property("opened").toBool(); }, 3000));
QTest::qWait(250);
XRAY_QUESTIONS(questions);
click(typed("ListItemType", "text", QStringLiteral("Delete configuration"), popupContent(drawer)));
QCOMPARE(questions.count(), 1);
QCOMPARE(questions.first().at(0).toString(), QStringLiteral("Delete configuration?"));
QCOMPARE(questions.first().at(1).toString(), QStringLiteral("This action cannot be undone."));
QCOMPARE(questions.first().at(2).toString(), QStringLiteral("Delete"));
QCOMPARE(questions.first().at(3).toString(), QStringLiteral("Cancel"));
answer(questions, true);
QCOMPARE(removed.count(), 1);
QCOMPARE(m_snapshots->rowCount(), 0);
}
void TestXrayPagesUi::snapshotsDeleteCancelled()
{
m_snapshots->createFromCurrentModel();
XRAY_LOAD("PageProtocolXraySnapshots");
QObject *drawer = popupOf(m_page);
click(typed("ListItemType", "text", QStringLiteral("XHTTP Reality")));
QVERIFY(QTest::qWaitFor([drawer] { return drawer->property("opened").toBool(); }, 3000));
QTest::qWait(250);
XRAY_QUESTIONS(questions);
click(typed("ListItemType", "text", QStringLiteral("Delete configuration"), popupContent(drawer)));
QCOMPARE(questions.count(), 1);
answer(questions, false);
QCOMPARE(m_snapshots->rowCount(), 1);
}
void TestXrayPagesUi::snapshotsImportExport()
{
m_snapshots->createFromCurrentModel();
QCOMPARE(m_snapshots->rowCount(), 1);
const QString json = m_snapshots->exportToJson(0);
const QJsonObject exported = QJsonDocument::fromJson(json.toUtf8()).object();
for (const char *key : { "id", "displayName", "createdAt", "serverConfig" })
QVERIFY2(exported.contains(QLatin1String(key)), key);
QCOMPARE(exported.value("displayName").toString(), QStringLiteral("XHTTP Reality"));
QVERIFY(m_snapshots->importFromJson(json));
QCOMPARE(m_snapshots->rowCount(), 2);
XRAY_LOAD("PageProtocolXraySnapshots");
QSignalSpy notes(m_pages, &PageController::showNotificationMessage);
QSignalSpy importFailed(m_snapshots, &XrayConfigSnapshotsModel::importFailed);
QVERIFY(!m_snapshots->importFromJson(QStringLiteral("[]")));
QCOMPARE(importFailed.count(), 1);
QCOMPARE(importFailed.first().at(0).toString(), QStringLiteral("Invalid JSON format"));
QCOMPARE(notes.count(), 1);
QCOMPARE(notes.first().at(0).toString(), QStringLiteral("Invalid JSON format"));
QCOMPARE(m_snapshots->rowCount(), 2);
}
void TestXrayPagesUi::snapshotsExportWithoutSnapshots()
{
XRAY_LOAD("PageProtocolXraySnapshots");
QSignalSpy busy(m_pages, &PageController::showBusyIndicator);
QSignalSpy notes(m_pages, &PageController::showNotificationMessage);
click(typed("ListItemType", "text", QStringLiteral("Export settings")));
QTest::qWait(200);
QCOMPARE(busy.count(), 0);
QCOMPARE(notes.count(), 0);
}
-628
View File
@@ -1,628 +0,0 @@
#ifndef TEST_XRAY_PAGES_UI_H
#define TEST_XRAY_PAGES_UI_H
#include <QJSValue>
#include <QJsonObject>
#include <QKeySequence>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSignalSpy>
#include <QTest>
#include "core/models/containerConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/utils/serverConfigUtils.h"
#include "ui/controllers/selfhosted/installUiController.h"
#include "ui/controllers/serversUiController.h"
#include "secureQSettings.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols/xrayConfigSnapshotsModel.h"
#include "utils/testCoreController.h"
#include "utils/xrayMatrix.h"
#define XRAY_LOAD(page) \
do { \
loadPage(QStringLiteral(page)); \
if (QTest::currentTestFailed()) \
return; \
} while (0)
#define XRAY_QUESTIONS(name) \
QSignalSpy name(dialogs(), SIGNAL(questionRequested(QString, QString, QString, QString, QVariant, QVariant)))
class TestXrayPagesUi : public QObject
{
Q_OBJECT
private:
SecureQSettings *m_settings = nullptr;
QQmlApplicationEngine *m_engine = nullptr;
TestCoreController *m_core = nullptr;
XrayConfigModel *m_model = nullptr;
XrayConfigSnapshotsModel *m_snapshots = nullptr;
PageController *m_pages = nullptr;
QString m_serverId;
QString m_readOnlyServerId;
QString m_hostlessServerId;
QQuickWindow *m_window = nullptr;
QQuickItem *m_page = nullptr;
static amnezia::ContainerConfig xrayContainer(const amnezia::XrayServerConfig &srv)
{
amnezia::ContainerConfig container;
container.container = amnezia::DockerContainer::Xray;
amnezia::XrayProtocolConfig xray;
xray.serverConfig = srv;
amnezia::XrayClientConfig client;
client.nativeConfig = xraytest::compact(xraytest::clientJson(srv));
client.localPort = QStringLiteral("10808");
client.id = xraytest::clientId();
xray.setClientConfig(client);
container.protocolConfig = xray;
return container;
}
static QJsonObject serverJson(const QString &host, const QString &password, const amnezia::XrayServerConfig &srv)
{
amnezia::SelfHostedAdminServerConfig server;
server.hostName = host;
server.userName = QStringLiteral("root");
server.password = password;
server.port = 22;
server.description = QStringLiteral("xray ui test ") + (host.isEmpty() ? QStringLiteral("hostless") : host);
server.displayName = server.description;
server.defaultContainer = amnezia::DockerContainer::Xray;
server.containers.insert(amnezia::DockerContainer::Xray, xrayContainer(srv));
return server.toJson();
}
QString addServer(const QString &host, const QString &password, serverConfigUtils::ConfigType kind)
{
const int before = m_core->m_serversRepository->serversCount();
const QString id = m_core->m_serversRepository->addServer(
QString(), serverJson(host, password, xraytest::baseConfig("xhttp", "reality", "")), kind);
return m_core->m_serversRepository->serversCount() == before + 1 ? id : QString();
}
void storeAndOpen(const amnezia::XrayServerConfig &srv, const QString &serverId = QString())
{
const QString id = serverId.isEmpty() ? m_serverId : serverId;
const auto kind = m_core->m_serversRepository->serverKind(id);
const QString host = id == m_serverId ? QStringLiteral("203.0.113.10")
: id == m_readOnlyServerId ? QStringLiteral("203.0.113.11")
: QString();
const QString password = id == m_readOnlyServerId ? QString() : QStringLiteral("not-used");
m_core->m_serversRepository->editServer(id, serverJson(host, password, srv), kind);
m_core->m_installUiController->openServerSettings(id, static_cast<int>(amnezia::DockerContainer::Xray),
static_cast<int>(amnezia::Proto::Xray));
}
void openStored(const amnezia::XrayServerConfig &srv)
{
amnezia::XrayProtocolConfig cfg = *xrayContainer(srv).getXrayProtocolConfig();
m_model->updateModel(amnezia::DockerContainer::Xray, cfg);
}
amnezia::XrayServerConfig stored(const QString &serverId = QString()) const
{
const amnezia::ContainerConfig container = m_core->m_serversController->getContainerConfig(
serverId.isEmpty() ? m_serverId : serverId, amnezia::DockerContainer::Xray);
const auto *xray = container.getXrayProtocolConfig();
return xray ? xray->serverConfig : amnezia::XrayServerConfig {};
}
void selectServer(const QString &serverId)
{
m_core->m_serversUiController->updateModel();
m_core->m_serversUiController->setProcessedServerId(serverId);
m_core->m_serversUiController->setProcessedContainerIndex(static_cast<int>(amnezia::DockerContainer::Xray));
}
void loadPage(const QString &page)
{
closePage();
const QByteArray qml = QStringLiteral(
"import QtQuick\n"
"import QtQuick.Controls\n"
"import \"../Config\"\n"
"ApplicationWindow {\n"
" width: 420; height: 800; visible: true\n"
" property var dialogs: Dialogs\n"
" Loader { objectName: \"pageLoader\"; anchors.fill: parent; source: \"qrc:/ui/qml/Pages2/%1.qml\" }\n"
"}\n").arg(page).toUtf8();
m_engine->loadData(qml, QUrl(QStringLiteral("qrc:/ui/qml/Pages2/xrayUiTestHost.qml")));
QVERIFY2(!m_engine->rootObjects().isEmpty(), "test host window did not load");
m_window = qobject_cast<QQuickWindow *>(m_engine->rootObjects().last());
QVERIFY(m_window);
QVERIFY(QTest::qWaitForWindowExposed(m_window));
m_window->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(m_window));
auto *loader = m_window->findChild<QObject *>(QStringLiteral("pageLoader"));
QVERIFY(loader);
QTRY_VERIFY2(loader->property("status").toInt() == 1, qPrintable(page + QStringLiteral(" failed to load")));
m_page = qvariant_cast<QQuickItem *>(loader->property("item"));
QVERIFY(m_page);
}
void closePage()
{
m_page = nullptr;
if (m_window) {
m_window->close();
m_window->deleteLater();
m_window = nullptr;
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
}
QObject *dialogs() const
{
return m_window ? qvariant_cast<QObject *>(m_window->property("dialogs")) : nullptr;
}
static void answer(const QSignalSpy &questions, bool yes)
{
QJSValue fn = questions.last().at(yes ? 4 : 5).value<QJSValue>();
fn.call();
QTest::qWait(50);
}
static void collect(QQuickItem *item, QList<QQuickItem *> &out)
{
out.append(item);
for (QQuickItem *child : item->childItems())
collect(child, out);
}
QList<QQuickItem *> items(QQuickItem *root = nullptr) const
{
QList<QQuickItem *> out;
if (QQuickItem *start = root ? root : m_page)
collect(start, out);
return out;
}
static bool isType(const QObject *object, const char *qmlType)
{
return QString::fromLatin1(object->metaObject()->className()).startsWith(QLatin1String(qmlType));
}
QQuickItem *item(const char *property, const QString &value, QQuickItem *root = nullptr)
{
for (QQuickItem *candidate : items(root)) {
const QVariant v = candidate->property(property);
if (v.isValid() && v.toString() == value)
return candidate;
}
qWarning().noquote() << "no item with" << property << "=" << value;
return nullptr;
}
QQuickItem *visibleItem(const char *property, const QString &value, QQuickItem *root = nullptr)
{
for (QQuickItem *candidate : items(root)) {
const QVariant v = candidate->property(property);
if (v.isValid() && v.toString() == value && candidate->isVisible())
return candidate;
}
qWarning().noquote() << "no visible item with" << property << "=" << value;
return nullptr;
}
QQuickItem *typed(const char *qmlType, const char *property, const QString &value, QQuickItem *root = nullptr)
{
for (QQuickItem *candidate : items(root)) {
if (!isType(candidate, qmlType))
continue;
const QVariant v = candidate->property(property);
if (v.isValid() && v.toString() == value)
return candidate;
}
qWarning().noquote() << "no" << qmlType << "with" << property << "=" << value;
return nullptr;
}
QList<QQuickItem *> allOfType(const char *qmlType, QQuickItem *root = nullptr)
{
QList<QQuickItem *> out;
for (QQuickItem *candidate : items(root)) {
if (isType(candidate, qmlType))
out.append(candidate);
}
return out;
}
static QQuickItem *input(QQuickItem *field)
{
return field ? qvariant_cast<QQuickItem *>(field->property("textField")) : nullptr;
}
static QQuickItem *ownerField(QQuickItem *textInput)
{
QQuickItem *p = textInput ? textInput->parentItem() : nullptr;
while (p && !isType(p, "TextFieldType"))
p = p->parentItem();
return p;
}
QQuickItem *field(const QString &headerText)
{
return input(typed("TextFieldType", "headerText", headerText));
}
QQuickItem *visibleField(const QString &headerText)
{
for (QQuickItem *candidate : allOfType("TextFieldType")) {
if (candidate->property("headerText").toString() == headerText && candidate->isVisible())
return input(candidate);
}
qWarning().noquote() << "no visible TextFieldType" << headerText;
return nullptr;
}
QQuickItem *dropDown(const QString &descriptionText)
{
for (QQuickItem *candidate : allOfType("DropDownType")) {
if (candidate->property("descriptionText").toString() == descriptionText && candidate->isVisible())
return candidate;
}
return typed("DropDownType", "descriptionText", descriptionText);
}
QQuickItem *minMaxRow(int index) { return allOfType("MinMaxRowType").value(index); }
QQuickItem *minMaxInput(QQuickItem *row, const QString &which)
{
return row ? input(typed("TextFieldType", "headerText", which, row)) : nullptr;
}
QQuickItem *iconButton(const QString &iconSuffix)
{
for (QQuickItem *candidate : items()) {
QQmlProperty source(candidate, QStringLiteral("icon.source"));
if (source.isValid() && source.read().toUrl().toString().endsWith(iconSuffix))
return candidate;
}
qWarning().noquote() << "no icon button" << iconSuffix;
return nullptr;
}
QQuickItem *saveButton() { return typed("ButtonType", "text", QStringLiteral("Save")); }
QQuickItem *switcherOf(QQuickItem *switchRow)
{
return switchRow ? qvariant_cast<QQuickItem *>(switchRow->property("switcher")) : nullptr;
}
void scrollIntoView(QQuickItem *target)
{
for (QQuickItem *p = target->parentItem(); p; p = p->parentItem()) {
if (!p->property("contentY").isValid() || !p->property("contentHeight").isValid())
continue;
auto *content = qvariant_cast<QQuickItem *>(p->property("contentItem"));
if (!content)
continue;
const qreal inView = target->mapToItem(p, QPointF(0, 0)).y();
if (inView >= 0 && inView + target->height() <= p->height())
return;
const qreal originY = p->property("originY").toReal();
const qreal y = target->mapToItem(content, QPointF(0, 0)).y();
const qreal maxY = qMax<qreal>(originY, originY + p->property("contentHeight").toReal() - p->height());
p->setProperty("contentY", qBound<qreal>(originY, y - p->height() / 3, maxY));
QTest::qWait(50);
return;
}
}
static void polishAncestors(QQuickItem *item)
{
QList<QQuickItem *> chain;
for (QQuickItem *p = item; p; p = p->parentItem())
chain.prepend(p);
for (QQuickItem *p : chain)
p->ensurePolished();
}
void click(QQuickItem *target)
{
polishAncestors(target);
scrollIntoView(target);
const QPointF center = target->mapToScene(QPointF(target->width() / 2, target->height() / 2));
QTest::mouseClick(m_window, Qt::LeftButton, {}, center.toPoint());
QTest::qWait(50);
}
void type(const QString &text)
{
for (const QChar ch : text)
QTest::keyClick(m_window, ch.toLatin1());
}
void key(Qt::Key k)
{
QTest::keyClick(m_window, k);
QTest::qWait(30);
}
void clearAndType(QQuickItem *textInput, const QString &text, bool commit = true)
{
click(textInput);
QTest::keySequence(m_window, QKeySequence::SelectAll);
QTest::keyClick(m_window, Qt::Key_Backspace);
type(text);
if (commit)
key(Qt::Key_Return);
QTest::qWait(30);
}
void blur(QQuickItem *otherInput) { click(otherInput); }
static QObject *popupOf(QObject *owner)
{
if (!owner)
return nullptr;
for (QObject *child : owner->findChildren<QObject *>()) {
if (child->inherits("QQuickPopup"))
return child;
}
return nullptr;
}
static QQuickItem *popupContent(QObject *popup)
{
return popup ? qvariant_cast<QQuickItem *>(popup->property("contentItem")) : nullptr;
}
QObject *openDropDown(QQuickItem *dd)
{
QObject *popup = popupOf(dd);
if (!popup)
return nullptr;
for (int attempt = 0; attempt < 2; ++attempt) {
QTest::qWait(100);
click(dd);
if (QTest::qWaitFor([popup] { return popup->property("opened").toBool(); }, 1500)) {
QTest::qWait(250);
return popup;
}
}
return nullptr;
}
bool closeWithEscape(QObject *popup)
{
key(Qt::Key_Escape);
return QTest::qWaitFor([popup] { return !popup->property("visible").toBool(); }, 3000);
}
QList<QQuickItem *> options(QObject *popup)
{
QList<QQuickItem *> out;
for (QQuickItem *candidate : allOfType("RadioItemType", popupContent(popup))) {
if (candidate->isVisible())
out.append(candidate);
}
return out;
}
bool chooseOption(QQuickItem *dd, const QString &option)
{
QObject *popup = openDropDown(dd);
if (!popup) {
qWarning().noquote() << "drop-down did not open";
return false;
}
for (QQuickItem *candidate : options(popup)) {
if (candidate->property("text").toString() == option) {
click(candidate);
return QTest::qWaitFor([popup] { return !popup->property("visible").toBool(); }, 3000);
}
}
qWarning().noquote() << "no option" << option;
closeWithEscape(popup);
return false;
}
QStringList optionNames(QQuickItem *dd, bool checkedOnly)
{
QStringList names;
QObject *popup = openDropDown(dd);
if (!popup)
return { QStringLiteral("<did not open>") };
for (QQuickItem *candidate : options(popup)) {
if (!checkedOnly || candidate->property("checked").toBool())
names << candidate->property("text").toString();
}
closeWithEscape(popup);
return names;
}
private slots:
void initTestCase();
void init();
void cleanup();
void cleanupTestCase();
void transportRadioClickChangesModel();
void packetUpFieldsFollowMode_data();
void packetUpFieldsFollowMode();
void pathGetsLeadingSlash();
void visionNeedsTlsOrReality_data();
void visionNeedsTlsOrReality();
void fingerprintHiddenOnMkcp_data();
void fingerprintHiddenOnMkcp();
void sniRejectsWildcardInput();
void xPaddingFieldsFollowObfsMode_data();
void xPaddingFieldsFollowObfsMode();
void subpageSaveRefusesInvalidSni_data();
void subpageSaveRefusesInvalidSni();
void mainPortPlaceholderAndDefault();
void mainPortInput_data();
void mainPortInput();
void mainPortEditTogglesSave();
void mainEmptyPortDisablesSave();
void mainEmptyPortErrorOnFocusOut();
void mainPortFollowsModelOnlyWhenUnfocused();
void mainSaveRefusesInvalidPort();
void mainSaveJoinsErrors();
void mainPortChangeDialog();
void mainTransportSubtitle_data();
void mainTransportSubtitle();
void mainSecuritySubtitle_data();
void mainSecuritySubtitle();
void mainFlowSubtitle_data();
void mainFlowSubtitle();
void mainRowsNavigate_data();
void mainRowsNavigate();
void mainSnapshotsIconNavigates();
void mainBackButtonClosesPage();
void mainResetDialogTexts();
void mainResetAccepted();
void mainResetCancelled();
void mainKeyMigrationDialog();
void mainReadOnlyServer();
void mainWritableServerBaseline();
void transportXhttpRadioSubtitle();
void transportRadioCheckedFollowsModel_data();
void transportRadioCheckedFollowsModel();
void transportRadioReturnKey();
void transportSectionVisibility_data();
void transportSectionVisibility();
void transportRawToXhttpFillsDefaults();
void transportSwitchKeepsXhttpValues();
void transportPacketUpFieldsMoreRows_data();
void transportPacketUpFieldsMoreRows();
void transportPacketUpFieldsFollowModeLive();
void transportAlwaysVisibleXhttpControls_data();
void transportAlwaysVisibleXhttpControls();
void transportDropDownOptions_data();
void transportDropDownOptions();
void transportDropDownRoundTrip_data();
void transportDropDownRoundTrip();
void transportDropDownEscape();
void transportChoosingCurrentModeIsNoChange();
void transportPacketUpRealityAllowed();
void transportSwitches_data();
void transportSwitches();
void transportSwitchLabelToggles();
void transportHostFilter();
void transportHostValidity_data();
void transportHostValidity();
void transportEmptyHost();
void transportSaveRefusesInvalidHost();
void transportPathFilter();
void transportPathEdgeCases_data();
void transportPathEdgeCases();
void transportKeyFieldsFilter_data();
void transportKeyFieldsFilter();
void transportNumberFields_data();
void transportNumberFields();
void transportMinMaxRows_data();
void transportMinMaxRows();
void transportMinMaxOverflow();
void transportMkcpPlaceholders_data();
void transportMkcpPlaceholders();
void transportMkcpClamps_data();
void transportMkcpClamps();
void transportMtuRaisesSendingWindow_data();
void transportMtuRaisesSendingWindow();
void transportSaveDialogDescription_data();
void transportSaveDialogDescription();
void transportSubpageRows();
void transportReadOnly();
void securityMatrix_data();
void securityMatrix();
void securityRadioRoundTrip();
void securityRealityNotClickableOnMkcp();
void securityMkcpStoredReality();
void securityDropDownOptions_data();
void securityDropDownOptions();
void securityDropDownRoundTrip_data();
void securityDropDownRoundTrip();
void securityFingerprintSharedBetweenBlocks();
void securityValidSni();
void securitySniSiteCoupling();
void securityInvalidSni_data();
void securityInvalidSni();
void securityIpv4Sni();
void securitySniFilter();
void securityEmptySni();
void securitySniErrorOnFocusOut();
void securityNoneIgnoresSni();
void securitySaveDialogDescription_data();
void securitySaveDialogDescription();
void flowRadioRoundTrip();
void flowUdp443Subtitle();
void flowVisionNotAllowed_data();
void flowVisionNotAllowed();
void flowEmptyWhenVisionNotAllowed();
void flowDeviceOrServerChange_data();
void flowDeviceOrServerChange();
void flowEmptyTransportCountsAsRaw();
void flowUnknownStoredFlow();
void flowKeyboard();
void paddingInitialState();
void paddingSwitchTogglesFields();
void paddingSwitchLabelToggles();
void paddingBytesSubtitle_data();
void paddingBytesSubtitle();
void paddingBytesRowNavigates_data();
void paddingBytesRowNavigates();
void paddingKeyHeaderFilter_data();
void paddingKeyHeaderFilter();
void paddingDropDownRoundTrip_data();
void paddingDropDownRoundTrip();
void paddingDisabledDropDownDoesNotOpen();
void paddingBytesInitialState();
void paddingBytesRange_data();
void paddingBytesRange();
void paddingBytesOverflowAndFilter();
void xmuxInitialState();
void xmuxSwitchDisablesFields();
void xmuxMinRaisesMax();
void xmuxMaxBelowMin();
void xmuxBothLimitsRefusedOnSave();
void xmuxZeroCountsAsUnset();
void xmuxDisabledIsNotValidated();
void xmuxTwoErrorsAtOnce();
void xmuxKeepAlive_data();
void xmuxKeepAlive();
void xmuxEditIsDeviceOnly();
void saveInitiallyDisabled_data();
void saveInitiallyDisabled();
void saveEditThenRevert();
void saveReloadDiscardsEdits();
void saveCancelDoesNothing();
void saveDeviceOnlyContinue_data();
void saveDeviceOnlyContinue();
void saveServerChangeOnHostlessServer();
void saveCppGuardRefusesInvalid();
void saveReadOnlySubpages_data();
void saveReadOnlySubpages();
void snapshotsEmptyState();
void snapshotsCreate();
void snapshotsApply();
void snapshotsDelete();
void snapshotsDeleteCancelled();
void snapshotsImportExport();
void snapshotsExportWithoutSnapshots();
void issueTransportRoundTripLeavesSaveEnabled();
void issueSubpageSavesEmptyPort();
void issueHiddenRealitySniBlocksMkcpSave();
void issueClearingXmuxZeroEnablesSave();
void issueSnapshotWithoutPortApplyIsSilent();
void issueSaveWithoutReturnSkipsClamp();
void issueSaveWithoutReturnKeepsRawKeepAlive();
};
#endif