girder 5.0.19.dev60__tar.gz → 5.0.19.dev76__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/config.yml +27 -7
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.pre-commit-config.yaml +1 -0
- {girder-5.0.19.dev60/girder.egg-info → girder-5.0.19.dev76}/PKG-INFO +1 -1
- girder-5.0.19.dev76/girder/utility/_hash_state.py +261 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/playwright-teardown.ts +2 -2
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_autojoin.spec.ts +100 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_hashsum_download.spec.ts +74 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_homepage.spec.ts +118 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_item_licenses.spec.ts +257 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_jobs.spec.ts +562 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_readme.spec.ts +52 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_terms.spec.ts +263 -0
- girder-5.0.19.dev76/girder/web/tests/spec/plugin_user_quota.spec.ts +434 -0
- girder-5.0.19.dev76/girder/web/tests/spec/utilities.spec.ts +434 -0
- girder-5.0.19.dev76/girder/web/tests/spec/version.spec.ts +60 -0
- girder-5.0.19.dev76/girder/web/tests/spec/widgets.spec.ts +290 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76/girder.egg-info}/PKG-INFO +1 -1
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/SOURCES.txt +5 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/scm_file_list.json +5 -0
- girder-5.0.19.dev76/girder.egg-info/scm_version.json +8 -0
- girder-5.0.19.dev60/girder/utility/_hash_state.py +0 -152
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_hashsum_download.spec.ts +0 -26
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_homepage.spec.ts +0 -37
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_item_licenses.spec.ts +0 -26
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_readme.spec.ts +0 -24
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_terms.spec.ts +0 -38
- girder-5.0.19.dev60/girder/web/tests/spec/plugin_user_quota.spec.ts +0 -39
- girder-5.0.19.dev60/girder.egg-info/scm_version.json +0 -8
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/Dockerfile +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/build_plugins.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/generatePyEnvChecksum.sh +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/publish_npm.sh +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/publish_pypi.sh +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.circleci/rabbitmq.conf +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.codecov.yml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.dockerignore +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.editorconfig +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.git-blame-ignore-revs +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.github/workflows/codespell.yml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.gitignore +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/.readthedocs.yml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/CHANGELOG.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/CMakeLists.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/CONTRIBUTING.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/Dockerfile +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/LICENSE +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/MANIFEST.in +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/README.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/README.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/girder_client/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/girder_client/_jsonemitter.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/girder_client/cli.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/pyproject.toml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/clients/python/setup.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docker-compose.yml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/admin-docs.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/api-docs.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/build-docs.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/conf.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/configuration.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/dependencies.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/deployment.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/dev-installation.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/developer-cookbook.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/developer-docs.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/development.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/favicon.ico +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/images/dicom-viewer.png +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/index.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/license.png +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/list_env_settings.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/list_env_settings.yaml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/migration-guide.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/mount.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/plugin-development.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/plugins.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/python-client.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/requirements-docs.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/security.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/sftp.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/static/custom.css +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/user-docs.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/docs/user-guide.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/access.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/api_docs.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/api_main.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/describe.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/docs.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/filter_logging.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/rest.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/sftp.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/api_key.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/assetstore.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/collection.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/file.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/folder.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/group.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/item.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/resource.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/system.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/token.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/api/v1/user.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/asgi.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/cli/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/cli/mount.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/cli/serve.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/cli/sftpd.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/cli/shell.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/constants.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/events.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/exceptions.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/_footer.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/_header.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/accountApproval.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/accountApproved.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/emailVerification.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/groupInvite.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/mail_templates/temporaryAccess.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/api_key.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/assetstore.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/collection.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/file.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/folder.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/group.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/item.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/model_base.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/setting.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/token.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/upload.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/models/user.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/notification.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/plugin.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/settings.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/tasks.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/_cache.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/abstract_assetstore_adapter.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/acl_mixin.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/assetstore_utilities.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/config.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/error.mako +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/filesystem_assetstore_adapter.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/mail_utils.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/model_importer.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/path.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/progress.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/s3_assetstore_adapter.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/search.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/server.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/setting_utilities.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/system.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/webroot.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/utility/ziputil.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/.env.development +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/.gitignore +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/.prettierrc.json +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/README.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/Girder_Favicon.png +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/Girder_Mark.png +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/fontello-BGLM866c.svg +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/fontello-CMxI46LA.eot +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/fontello-Ddaw8Z2O.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/fontello-KuItqCW9.ttf +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/fontello-_lT35XxX.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/glyphicons-halflings-regular-BKjkU69z.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/glyphicons-halflings-regular-BUJKDMgK.eot +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/glyphicons-halflings-regular-CSehLiBc.svg +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/glyphicons-halflings-regular-DrwTMapi.ttf +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/glyphicons-halflings-regular-DzqM6ju8.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/index-C3KtNRrl.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/index-C3KtNRrl.js.map +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/index-DFhDyJFH.css +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/jsoneditor-icons-CIlYC0TY.svg +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-300-B6tDWRxp.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-300-DZbNTRe6.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-300italic-DLsXqxhU.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-300italic-Dfz31Up5.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-400-D5zbb_EN.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-400-DLgeRt71.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-400italic-DpGQ6Ktj.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-400italic-UJbezL8h.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-600-CCvT4JHQ.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-600-DHWfDY-G.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-600italic-X8kCjSq_.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-600italic-slqXzGub.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-700-CsbQbrjx.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-700-DNl7WOH5.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-700italic-BtE7VvhE.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-700italic-hc2P_kZo.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-800-CbJOM4_m.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-800-XaYTAPrv.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-800italic-DqvYew-K.woff +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/assets/open-sans-latin-800italic-JIGkTxpX.woff2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/index.html +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/auth.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/constants.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/dialog.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/events.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/index.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/main.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/misc.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/pluginUtils.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/rest.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/router.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/routes.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/version.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/src/src/vite-env.d.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/swagger-ui/swagger-ui-bundle.js +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/dist/swagger-ui/swagger-ui.css +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/env.d.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/playwright.config.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/coverage.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/playwright-setup.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/server.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/admin.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/browser.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/collection.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/collectionBaseClass.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/customWidgets.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/README.md +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/fake.jpg +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/nomime.dat +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/slicer_cli_web_multiple_test.xml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/slicer_cli_web_test.xml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/ten_byte_file.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/testFile.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data/testFile2 +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/data.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/emptyLayout.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/file_upload.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/folder.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/group.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/item.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/model.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_authorized_upload.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_config_view.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_container_view.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_item_selector.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_panel_group.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_parser.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_task.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_widget_model.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_slicer_cli_web_widget_view.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/plugin_thumbnails.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/routing.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/setApi.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/setting_show_download.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/sortWidget.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/swagger.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/spec/user.spec.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/web/tests/util.ts +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/worker_plugin.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder/wsgi.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/dependency_links.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/entry_points.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/not-zip-safe +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/requires.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/girder.egg-info/top_level.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/jsdoc.json +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/package-lock.json +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/package.json +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pyproject.toml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/LICENSE +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/MANIFEST.in +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pyproject.toml +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/__init__.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/assertions.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/fixtures.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/plugin.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/plugin_registry.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/utils.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/pytest_girder/web_client.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/pytest_girder/setup.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/requirements-dev.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/midas/README.rst +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/midas/migrate.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/midas/requirements.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/midas/walk_girder.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/midas/walk_midas.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/publicNames.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/publicNames.txt +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/scripts/test_names.sh +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/setup.cfg +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/setup.py +0 -0
- {girder-5.0.19.dev60 → girder-5.0.19.dev76}/tox.ini +0 -0
|
@@ -175,6 +175,11 @@ jobs:
|
|
|
175
175
|
web-test:
|
|
176
176
|
executor: py3-mongo
|
|
177
177
|
resource_class: large
|
|
178
|
+
parameters:
|
|
179
|
+
browser:
|
|
180
|
+
description: "Playwright project / browser to run the web tests against"
|
|
181
|
+
type: enum
|
|
182
|
+
enum: [chromium, firefox]
|
|
178
183
|
steps:
|
|
179
184
|
- checkout:
|
|
180
185
|
path: girder
|
|
@@ -188,20 +193,26 @@ jobs:
|
|
|
188
193
|
- build-plugins
|
|
189
194
|
- run:
|
|
190
195
|
name: Install playwright browser dependencies
|
|
191
|
-
command:
|
|
196
|
+
command: npx playwright install --with-deps << parameters.browser >>
|
|
192
197
|
working_directory: girder/girder/web
|
|
193
198
|
- run:
|
|
194
|
-
name: Test @girder/core
|
|
195
|
-
command: npm run test
|
|
199
|
+
name: Test @girder/core (<< parameters.browser >>)
|
|
200
|
+
command: npm run test -- --project=<< parameters.browser >>
|
|
196
201
|
working_directory: girder/girder/web
|
|
197
202
|
environment:
|
|
198
203
|
PLAYWRIGHT_WORKERS: 4
|
|
204
|
+
GIRDER_TEST_COVERAGE_DIR: ../../build/test/coverage/<< parameters.browser >>
|
|
205
|
+
GIRDER_TEST_COVERAGE_FILE: cobertura-coverage.xml
|
|
199
206
|
- store_artifacts:
|
|
200
207
|
path: girder/girder/web/coverage/report
|
|
201
|
-
destination: coverage
|
|
208
|
+
destination: coverage-<< parameters.browser >>
|
|
202
209
|
- store_artifacts:
|
|
203
210
|
path: girder/girder/web/test-results
|
|
204
|
-
destination: test-results
|
|
211
|
+
destination: test-results-<< parameters.browser >>
|
|
212
|
+
- persist_to_workspace:
|
|
213
|
+
root: /home/circleci/project
|
|
214
|
+
paths:
|
|
215
|
+
- girder/build/test/coverage/<< parameters.browser >>
|
|
205
216
|
|
|
206
217
|
integration-test:
|
|
207
218
|
executor: machine
|
|
@@ -316,7 +327,7 @@ jobs:
|
|
|
316
327
|
working_directory: girder
|
|
317
328
|
- run:
|
|
318
329
|
name: Upload coverage
|
|
319
|
-
command: ./codecov --disable search pycov gcov --root girder --required --file build/test/coverage/py_coverage.xml build/test/coverage/cobertura-coverage.xml
|
|
330
|
+
command: ./codecov --disable search pycov gcov --root girder --required --file build/test/coverage/py_coverage.xml build/test/coverage/chromium/cobertura-coverage.xml build/test/coverage/firefox/cobertura-coverage.xml
|
|
320
331
|
working_directory: girder
|
|
321
332
|
|
|
322
333
|
dockers:
|
|
@@ -424,6 +435,14 @@ workflows:
|
|
|
424
435
|
tags:
|
|
425
436
|
only: /^v[0-9]+.*/
|
|
426
437
|
- web-test:
|
|
438
|
+
name: web-test-chromium
|
|
439
|
+
browser: chromium
|
|
440
|
+
filters:
|
|
441
|
+
tags:
|
|
442
|
+
only: /^v[0-9]+.*/
|
|
443
|
+
- web-test:
|
|
444
|
+
name: web-test-firefox
|
|
445
|
+
browser: firefox
|
|
427
446
|
filters:
|
|
428
447
|
tags:
|
|
429
448
|
only: /^v[0-9]+.*/
|
|
@@ -435,7 +454,8 @@ workflows:
|
|
|
435
454
|
requires:
|
|
436
455
|
- server-pytest-test
|
|
437
456
|
- server-legacy-test
|
|
438
|
-
- web-test
|
|
457
|
+
- web-test-chromium
|
|
458
|
+
- web-test-firefox
|
|
439
459
|
- integration-test
|
|
440
460
|
filters:
|
|
441
461
|
tags:
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Utility for saving and restoring the internal state of a hash object
|
|
2
|
+
# so that checksums can be streamed without having to remain in memory.
|
|
3
|
+
# Inspired by http://code.activestate.com/recipes/
|
|
4
|
+
# 578479-save-and-restore-sha-512-internal-state/
|
|
5
|
+
|
|
6
|
+
import binascii
|
|
7
|
+
import ctypes
|
|
8
|
+
import hashlib
|
|
9
|
+
import ssl
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
_ver = sys.version_info
|
|
13
|
+
_free_threaded = hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled()
|
|
14
|
+
_WORD = ctypes.sizeof(ctypes.c_void_p)
|
|
15
|
+
|
|
16
|
+
# This is the offset to the EVP_MD_CTX structure in CPython's _hashopenssl.c
|
|
17
|
+
# EVPObject. It changed for Python 3.8 in
|
|
18
|
+
# https://github.com/python/cpython/pull/16023 and again for the free-threaded
|
|
19
|
+
# builds in 3.14 (which add two words to the object header) and for all builds
|
|
20
|
+
# in 3.15 (which add one word). These values are only used as a fallback on
|
|
21
|
+
# platforms where we cannot safely auto-detect the layout.
|
|
22
|
+
if _free_threaded:
|
|
23
|
+
_HASHLIB_EVP_STRUCT_OFFSET = 5 if _ver >= (3, 15) else 4
|
|
24
|
+
else:
|
|
25
|
+
_HASHLIB_EVP_STRUCT_OFFSET = 3 if _ver >= (3, 15) else 2
|
|
26
|
+
|
|
27
|
+
# Number of pointer-sized words to scan when auto-detecting the layout of
|
|
28
|
+
# CPython's _hashlib.HASH object and the OpenSSL EVP_MD_CTX structure it
|
|
29
|
+
# references. The digest state has never been stored inline in the hash
|
|
30
|
+
# object (since CPython 2.7.13), so it is always reachable via two levels of
|
|
31
|
+
# pointers: the hash object points to an EVP_MD_CTX, which points to the
|
|
32
|
+
# state data.
|
|
33
|
+
_HASHLIB_OBJECT_SCAN_WORDS = 10
|
|
34
|
+
_EVP_MD_CTX_SCAN_WORDS = 16
|
|
35
|
+
|
|
36
|
+
# Minimum plausible heap address; anything smaller is not a real pointer.
|
|
37
|
+
_MIN_VALID_ADDRESS = 0x1000
|
|
38
|
+
|
|
39
|
+
# Auto-detected offsets, in units of words: (offset of the EVP_MD_CTX pointer
|
|
40
|
+
# within the hash object, offset of the state data pointer within the
|
|
41
|
+
# EVP_MD_CTX). Set once the first hash object has been inspected and reused
|
|
42
|
+
# for all subsequent objects, since the layout is the same for every hash
|
|
43
|
+
# algorithm within a single interpreter build.
|
|
44
|
+
_STATE_OFFSETS = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _readPointer(address):
|
|
48
|
+
return ctypes.cast(address, ctypes.POINTER(ctypes.c_void_p))[0]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _getReadableAddressRanges():
|
|
52
|
+
"""
|
|
53
|
+
Get the readable memory ranges of the current process, used to validate
|
|
54
|
+
candidate pointers before dereferencing them.
|
|
55
|
+
|
|
56
|
+
:returns: A list of (start, end) address tuples, or None if the platform
|
|
57
|
+
does not provide the information (in which case hardcoded offsets are
|
|
58
|
+
used instead).
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
with open('/proc/self/maps') as mapsFile:
|
|
62
|
+
ranges = []
|
|
63
|
+
for line in mapsFile:
|
|
64
|
+
fields = line.split()
|
|
65
|
+
if 'r' not in fields[1]:
|
|
66
|
+
continue
|
|
67
|
+
start, end = fields[0].split('-')
|
|
68
|
+
ranges.append((int(start, 16), int(end, 16)))
|
|
69
|
+
return ranges
|
|
70
|
+
except (OSError, IndexError, ValueError):
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _isValidAddress(ranges, address, size):
|
|
75
|
+
"""
|
|
76
|
+
Check that an address range is safe to read without segfaulting.
|
|
77
|
+
"""
|
|
78
|
+
if not address or address < _MIN_VALID_ADDRESS:
|
|
79
|
+
return False
|
|
80
|
+
return any(start <= address and address + size <= end for start, end in ranges)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _locateStateDataAddress(hashObject, initVectorFirstWord, stateSize):
|
|
84
|
+
"""
|
|
85
|
+
Find the address of the internal hash state of a hash object.
|
|
86
|
+
|
|
87
|
+
When possible, this is done by scanning the hash object for a pointer to
|
|
88
|
+
an OpenSSL EVP_MD_CTX and scanning that for a pointer to memory that
|
|
89
|
+
begins with the algorithm's initialization vector. This avoids depending
|
|
90
|
+
on hardcoded struct offsets, which CPython has repeatedly changed (see
|
|
91
|
+
the fallback offsets below the imports). On platforms where memory
|
|
92
|
+
ranges cannot be enumerated, the hardcoded offsets are used instead.
|
|
93
|
+
|
|
94
|
+
:param hashObject: A hashlib hash object backed by OpenSSL
|
|
95
|
+
(_hashlib.HASH).
|
|
96
|
+
:param initVectorFirstWord: The first eight bytes of the digest state of a
|
|
97
|
+
freshly initialized hash object of this algorithm.
|
|
98
|
+
:param stateSize: The size in bytes of the digest state.
|
|
99
|
+
:return: The address of the digest state, or None if it could not be
|
|
100
|
+
located.
|
|
101
|
+
"""
|
|
102
|
+
global _STATE_OFFSETS
|
|
103
|
+
objectAddress = id(hashObject)
|
|
104
|
+
|
|
105
|
+
if _STATE_OFFSETS is not None:
|
|
106
|
+
objectOffset, ctxOffset = _STATE_OFFSETS
|
|
107
|
+
ctxAddress = _readPointer(objectAddress + objectOffset * _WORD)
|
|
108
|
+
return _readPointer(ctxAddress + ctxOffset * _WORD)
|
|
109
|
+
|
|
110
|
+
ranges = _getReadableAddressRanges()
|
|
111
|
+
if ranges is None:
|
|
112
|
+
# Platforms without /proc/self/maps, such as macOS and Windows.
|
|
113
|
+
ctxAddress = _readPointer(objectAddress + _HASHLIB_EVP_STRUCT_OFFSET * _WORD)
|
|
114
|
+
if ssl._OPENSSL_API_VERSION < (3, 0):
|
|
115
|
+
# OpenSSL 1.x
|
|
116
|
+
return _readPointer(ctxAddress + 3 * _WORD)
|
|
117
|
+
# OpenSSL 3.x
|
|
118
|
+
return _readPointer(ctxAddress + 7 * _WORD)
|
|
119
|
+
|
|
120
|
+
for objectOffset in range(2, _HASHLIB_OBJECT_SCAN_WORDS):
|
|
121
|
+
ctxAddress = _readPointer(objectAddress + objectOffset * _WORD)
|
|
122
|
+
if not _isValidAddress(ranges, ctxAddress, _EVP_MD_CTX_SCAN_WORDS * _WORD):
|
|
123
|
+
continue
|
|
124
|
+
for ctxOffset in range(_EVP_MD_CTX_SCAN_WORDS):
|
|
125
|
+
stateAddress = _readPointer(ctxAddress + ctxOffset * _WORD)
|
|
126
|
+
if not _isValidAddress(ranges, stateAddress, stateSize):
|
|
127
|
+
continue
|
|
128
|
+
if ctypes.cast(
|
|
129
|
+
stateAddress, ctypes.POINTER(ctypes.c_char)
|
|
130
|
+
)[:8] == initVectorFirstWord:
|
|
131
|
+
_STATE_OFFSETS = (objectOffset, ctxOffset)
|
|
132
|
+
return stateAddress
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _getStateDataPointer(hashObject, hashInfo):
|
|
137
|
+
"""
|
|
138
|
+
Get a pointer to the internal hash state of a hash object.
|
|
139
|
+
|
|
140
|
+
:param hashObject: A hashlib hash object.
|
|
141
|
+
:param hashInfo: The _HashInfo instance describing the hash algorithm.
|
|
142
|
+
:return: A ctypes pointer to the internal hash state.
|
|
143
|
+
:rtype: ctypes.POINTER(ctypes.c_char)
|
|
144
|
+
"""
|
|
145
|
+
stateAddress = _locateStateDataAddress(
|
|
146
|
+
hashObject, hashInfo.initVectorFirstWord, hashInfo.stateSize)
|
|
147
|
+
if stateAddress is None:
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
'Could not locate the internal state of the %s hash object. This module '
|
|
150
|
+
'requires a CPython build with OpenSSL-backed hashlib.' % hashInfo.name)
|
|
151
|
+
return ctypes.cast(stateAddress, ctypes.POINTER(ctypes.c_char))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
_HASH_INFOS = {}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _HashInfo:
|
|
158
|
+
def __init__(self, type, stateSize, initVectorFirstWord):
|
|
159
|
+
self.type = type
|
|
160
|
+
self.stateSize = stateSize
|
|
161
|
+
self.initVectorFirstWord = initVectorFirstWord
|
|
162
|
+
|
|
163
|
+
# Instantiate to get the name and do a sanity check
|
|
164
|
+
instance = self.type()
|
|
165
|
+
self.name = instance.name
|
|
166
|
+
|
|
167
|
+
stateDataPointer = _getStateDataPointer(instance, self)
|
|
168
|
+
assert stateDataPointer[:8] == initVectorFirstWord
|
|
169
|
+
|
|
170
|
+
_HASH_INFOS[self.name] = self
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_HashInfo(
|
|
174
|
+
type=hashlib.md5,
|
|
175
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/md5/md5.h#L100
|
|
176
|
+
stateSize=92,
|
|
177
|
+
# Found empirically
|
|
178
|
+
initVectorFirstWord=b'\x01\x23\x45\x67\x89\xab\xcd\xef'
|
|
179
|
+
)
|
|
180
|
+
_HashInfo(
|
|
181
|
+
type=hashlib.sha1,
|
|
182
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha.h#L100
|
|
183
|
+
stateSize=96,
|
|
184
|
+
# Found empirically
|
|
185
|
+
initVectorFirstWord=b'\x01\x23\x45\x67\x89\xab\xcd\xef'
|
|
186
|
+
)
|
|
187
|
+
_HashInfo(
|
|
188
|
+
type=hashlib.sha224,
|
|
189
|
+
# Uses the same struct as sha256
|
|
190
|
+
stateSize=112,
|
|
191
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha256.c#L22
|
|
192
|
+
initVectorFirstWord=b'\xd8\x9e\x05\xc1\x07\xd5\x7c\x36'
|
|
193
|
+
)
|
|
194
|
+
_HashInfo(
|
|
195
|
+
type=hashlib.sha256,
|
|
196
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha.h#L135
|
|
197
|
+
stateSize=112,
|
|
198
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha256.c#L33
|
|
199
|
+
initVectorFirstWord=b'\x67\xe6\x09\x6a\x85\xae\x67\xbb'
|
|
200
|
+
)
|
|
201
|
+
_HashInfo(
|
|
202
|
+
type=hashlib.sha384,
|
|
203
|
+
# Uses the same struct as sha512
|
|
204
|
+
stateSize=216,
|
|
205
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha512.c#L64
|
|
206
|
+
initVectorFirstWord=b'\xd8\x9e\x05\xc1\x5d\x9d\xbb\xcb'
|
|
207
|
+
)
|
|
208
|
+
_HashInfo(
|
|
209
|
+
type=hashlib.sha512,
|
|
210
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha.h#L182
|
|
211
|
+
stateSize=216,
|
|
212
|
+
# github.com/openssl/openssl/blob/OpenSSL_1_0_1f/crypto/sha/sha512.c#L80
|
|
213
|
+
initVectorFirstWord=b'\x08\xc9\xbc\xf3\x67\xe6\x09\x6a'
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def serialize(hashObject):
|
|
218
|
+
"""
|
|
219
|
+
Serializes the internal state of the hash object passed in. Calling
|
|
220
|
+
restore() on the serialized value afterward will return an equivalent
|
|
221
|
+
hash object to the one passed.
|
|
222
|
+
|
|
223
|
+
:param hashObject: The hash object to serialize.
|
|
224
|
+
:type hashObject: _hashlib.HASH
|
|
225
|
+
:returns: A binary string representing the serialized state.
|
|
226
|
+
"""
|
|
227
|
+
hashInfo = _HASH_INFOS[hashObject.name]
|
|
228
|
+
stateDataPointer = _getStateDataPointer(hashObject, hashInfo)
|
|
229
|
+
return stateDataPointer[:hashInfo.stateSize]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def restore(oldStateData, hashName):
|
|
233
|
+
"""
|
|
234
|
+
Unserializes a hash object from a binary string created by the
|
|
235
|
+
serialize() method in this module.
|
|
236
|
+
|
|
237
|
+
:param oldStateData: The serialized state.
|
|
238
|
+
:type oldStateData: binary string
|
|
239
|
+
:param hashName: The name of the hash algorithm type.
|
|
240
|
+
:type hashName: str
|
|
241
|
+
:returns: A hash object in the same state as the one that serialize()
|
|
242
|
+
was called on.
|
|
243
|
+
"""
|
|
244
|
+
hashInfo = _HASH_INFOS[hashName]
|
|
245
|
+
assert len(oldStateData) == hashInfo.stateSize
|
|
246
|
+
|
|
247
|
+
hashObject = hashInfo.type()
|
|
248
|
+
stateDataPointer = _getStateDataPointer(hashObject, hashInfo)
|
|
249
|
+
|
|
250
|
+
for i, byte in enumerate(oldStateData):
|
|
251
|
+
stateDataPointer[i] = byte
|
|
252
|
+
|
|
253
|
+
return hashObject
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def serializeHex(hashObject):
|
|
257
|
+
return binascii.b2a_hex(serialize(hashObject))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def restoreHex(oldHexStateData, hashName):
|
|
261
|
+
return restore(binascii.a2b_hex(oldHexStateData), hashName)
|
|
@@ -33,7 +33,7 @@ export default async (_config: PlaywrightTestConfig) => {
|
|
|
33
33
|
|
|
34
34
|
report.execute(context);
|
|
35
35
|
|
|
36
|
-
const outputDir = '../../build/test/coverage';
|
|
36
|
+
const outputDir = process.env.GIRDER_TEST_COVERAGE_DIR || '../../build/test/coverage';
|
|
37
37
|
await fs.mkdir(outputDir, { recursive: true });
|
|
38
38
|
const contextXml = libReport.createContext({
|
|
39
39
|
dir: outputDir,
|
|
@@ -41,7 +41,7 @@ export default async (_config: PlaywrightTestConfig) => {
|
|
|
41
41
|
coverageMap,
|
|
42
42
|
});
|
|
43
43
|
const xmlReport = reports.create('cobertura', {
|
|
44
|
-
file: 'cobertura-coverage.xml',
|
|
44
|
+
file: process.env.GIRDER_TEST_COVERAGE_FILE || 'cobertura-coverage.xml',
|
|
45
45
|
skipEmpty: false,
|
|
46
46
|
});
|
|
47
47
|
xmlReport.execute(contextXml);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { expect, Page, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { setupServer } from '../server';
|
|
4
|
+
import { createUser, waitForIdlePage } from '../util';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ported from 3.x-maintenance: plugins/autojoin/plugin_tests/autojoinSpec.js
|
|
8
|
+
* Tests for the Auto Join plugin's configuration page and its effect on new
|
|
9
|
+
* users.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Navigate to the groups list page.
|
|
14
|
+
*/
|
|
15
|
+
async function gotoGroupsPage(page: Page) {
|
|
16
|
+
await page.locator('a.g-nav-link[g-target="groups"]').click();
|
|
17
|
+
await expect(page.locator('.g-group-search-form .g-search-field')).toBeVisible();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a private group and wait for its page to load. Assumes the user is
|
|
22
|
+
* logged in.
|
|
23
|
+
*/
|
|
24
|
+
async function createGroup(page: Page, name: string) {
|
|
25
|
+
await gotoGroupsPage(page);
|
|
26
|
+
await expect(page.locator('.g-group-create-button')).toBeVisible();
|
|
27
|
+
await page.locator('.g-group-create-button').click();
|
|
28
|
+
await expect(page.locator('#g-name')).toBeVisible();
|
|
29
|
+
await page.locator('#g-name').fill(name);
|
|
30
|
+
await page.locator('.g-save-group').click();
|
|
31
|
+
await waitForIdlePage(page);
|
|
32
|
+
await expect(page.locator('.g-group-name')).toHaveText(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Navigate to the Auto Join plugin configuration page.
|
|
37
|
+
*/
|
|
38
|
+
async function gotoAutojoinConfig(page: Page) {
|
|
39
|
+
await page.locator('a.g-nav-link[g-target="admin"]').click();
|
|
40
|
+
await page.locator('.g-plugins-config').click();
|
|
41
|
+
const configLink = page.locator('.g-plugin-list-item[data-name="autojoin"] a.g-plugin-config-link');
|
|
42
|
+
await expect(configLink).toBeVisible();
|
|
43
|
+
await configLink.click();
|
|
44
|
+
// Wait for the group collection to populate the group select.
|
|
45
|
+
await expect(page.locator('#g-autojoin-group > option')).toHaveCount(4);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fill out and submit the "add rule" row of the Auto Join configuration page.
|
|
50
|
+
*/
|
|
51
|
+
async function addRule(page: Page, pattern: string, groupName: string, level: string) {
|
|
52
|
+
await page.locator('#g-autojoin-pattern').fill(pattern);
|
|
53
|
+
await page.locator('#g-autojoin-group').selectOption({ label: groupName });
|
|
54
|
+
await page.locator('#g-autojoin-level').selectOption(level);
|
|
55
|
+
await page.locator('#g-autojoin-add').click();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test.describe('Test the auto join plugin', () => {
|
|
59
|
+
setupServer();
|
|
60
|
+
|
|
61
|
+
test('configure auto join rules', async ({ page }) => {
|
|
62
|
+
await createUser(page, 'admin', 'admin@girder.test', 'Joe', 'Admin', 'password');
|
|
63
|
+
|
|
64
|
+
await createGroup(page, 'group1');
|
|
65
|
+
await createGroup(page, 'group2');
|
|
66
|
+
await createGroup(page, 'group3');
|
|
67
|
+
|
|
68
|
+
await gotoAutojoinConfig(page);
|
|
69
|
+
|
|
70
|
+
await addRule(page, '@girder1.test', 'group1', '2');
|
|
71
|
+
await addRule(page, '@girder2.test', 'group2', '1');
|
|
72
|
+
await addRule(page, '@girder2.test', 'group3', '0');
|
|
73
|
+
|
|
74
|
+
await expect(page.locator('.g-autojoin-container tbody tr')).toHaveCount(3);
|
|
75
|
+
await expect(page.locator('.g-autojoin-container')).toContainText('@girder1.test');
|
|
76
|
+
await expect(page.locator('.g-autojoin-container')).toContainText('@girder2.test');
|
|
77
|
+
|
|
78
|
+
await page.locator('#g-autojoin-save').click();
|
|
79
|
+
await expect(page.locator('#g-alerts-container')).toContainText('Settings saved');
|
|
80
|
+
await waitForIdlePage(page);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('auto join a user whose email matches the girder2 rule', async ({ page }) => {
|
|
84
|
+
await createUser(page, 'user1', 'user1@girder2.test', 'Joe', 'User', 'password');
|
|
85
|
+
|
|
86
|
+
await gotoGroupsPage(page);
|
|
87
|
+
await expect(page.locator('.g-group-title', { hasText: 'group1' })).toHaveCount(0);
|
|
88
|
+
await expect(page.locator('.g-group-title', { hasText: 'group2' })).toHaveCount(1);
|
|
89
|
+
await expect(page.locator('.g-group-title', { hasText: 'group3' })).toHaveCount(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('auto join a user whose email matches the girder1 rule', async ({ page }) => {
|
|
93
|
+
await createUser(page, 'user2', 'user2@girder1.test', 'Joe', 'User', 'password');
|
|
94
|
+
|
|
95
|
+
await gotoGroupsPage(page);
|
|
96
|
+
await expect(page.locator('.g-group-title', { hasText: 'group1' })).toHaveCount(1);
|
|
97
|
+
await expect(page.locator('.g-group-title', { hasText: 'group2' })).toHaveCount(0);
|
|
98
|
+
await expect(page.locator('.g-group-title', { hasText: 'group3' })).toHaveCount(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
import { Page, expect, test } from '@playwright/test';
|
|
5
|
+
|
|
6
|
+
import { setupServer } from '../server';
|
|
7
|
+
import { createUser, upload, waitForIdlePage } from '../util';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Make a REST request from within the page context and await its result.
|
|
14
|
+
*/
|
|
15
|
+
async function asyncRestRequest(page: Page, opts: Record<string, unknown>) {
|
|
16
|
+
return await page.evaluate((requestOpts) => {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
// @ts-ignore - window.girder is available at runtime
|
|
19
|
+
window.girder.rest.restRequest(requestOpts).done((resp: unknown) => {
|
|
20
|
+
resolve(resp);
|
|
21
|
+
}).fail((resp: unknown) => {
|
|
22
|
+
reject(resp);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}, opts);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test.describe('Test the hashsum download front-end', () => {
|
|
29
|
+
setupServer();
|
|
30
|
+
|
|
31
|
+
test('verify presence of key file', async ({ page }) => {
|
|
32
|
+
await createUser(page, 'admin');
|
|
33
|
+
await page.locator('#g-app-header-container').getByText('admin').click();
|
|
34
|
+
await page.locator('a.g-my-folders').click();
|
|
35
|
+
await page.getByRole('link', { name: ' Private ' }).click();
|
|
36
|
+
await upload(page, path.join(__dirname, 'data', 'ten_byte_file.txt'));
|
|
37
|
+
|
|
38
|
+
await page.getByRole('link', { name: ' ten_byte_file.txt' }).click();
|
|
39
|
+
await page.getByTitle('Show info').click();
|
|
40
|
+
await expect(page.locator('input.g-hash-textbox').first()).toHaveValue('ff3245abe317049ed1b8aa7aa2f4c4dcb8bf86f083ed67eb26b43e2fbe3ba8fdf759f9e2f46fcf2a06c2dfeddf0cedcd41a68034cd618b880785b34f759d1a69')
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test.describe('Test the hashsum download configuration page', () => {
|
|
45
|
+
setupServer();
|
|
46
|
+
|
|
47
|
+
test('enable automatic checksum computation', async ({ page }) => {
|
|
48
|
+
await createUser(page, 'admin');
|
|
49
|
+
|
|
50
|
+
// Navigate to the plugin's admin settings page.
|
|
51
|
+
await page.locator('a.g-nav-link[g-target="admin"]').click();
|
|
52
|
+
await page.locator('.g-plugins-config').click();
|
|
53
|
+
const configLink = page.locator('.g-plugin-list-item[data-name="hashsum_download"] a.g-plugin-config-link');
|
|
54
|
+
await expect(configLink).toBeVisible();
|
|
55
|
+
await configLink.click();
|
|
56
|
+
|
|
57
|
+
const autoCompute = page.locator('#g-hashsum-download-auto-compute');
|
|
58
|
+
await expect(autoCompute).toBeVisible();
|
|
59
|
+
await expect(autoCompute).not.toBeChecked();
|
|
60
|
+
await autoCompute.check();
|
|
61
|
+
|
|
62
|
+
await page.locator('#g-hashsum-download-config-form input[type="submit"]').click();
|
|
63
|
+
await expect(page.locator('#g-alerts-container')).toContainText('Settings saved');
|
|
64
|
+
await waitForIdlePage(page);
|
|
65
|
+
|
|
66
|
+
// Verify that the setting was persisted on the server.
|
|
67
|
+
const settings = await asyncRestRequest(page, {
|
|
68
|
+
url: '/system/setting',
|
|
69
|
+
method: 'GET',
|
|
70
|
+
data: { list: JSON.stringify(['hashsum_download.auto_compute']) },
|
|
71
|
+
}) as Record<string, unknown>;
|
|
72
|
+
expect(settings['hashsum_download.auto_compute']).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Locator, Page, expect, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { setupServer } from '../server';
|
|
4
|
+
import { createUser, login, logout } from '../util';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ported from 3.x-maintenance: plugins/homepage/plugin_tests/homepageSpec.js
|
|
8
|
+
* Tests for the Homepage plugin's configuration page and its effect on the
|
|
9
|
+
* front page.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const markdown =
|
|
13
|
+
'It\'s very easy to make some words **bold** and other words *italic* with ' +
|
|
14
|
+
'Markdown. You can even [link to Girder!](https://girder.readthedocs.io/)';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Assert that the given container renders the expected markdown content.
|
|
18
|
+
*/
|
|
19
|
+
async function expectMarkdown(container: Locator) {
|
|
20
|
+
await expect(container.locator('p')).toContainText("It's very easy");
|
|
21
|
+
await expect(container.locator('strong')).toContainText('bold');
|
|
22
|
+
await expect(container.locator('em')).toContainText('italic');
|
|
23
|
+
await expect(container.locator('a[href="https://girder.readthedocs.io/"]')).toContainText('link to Girder!');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Navigate to the Homepage plugin configuration page.
|
|
28
|
+
*/
|
|
29
|
+
async function gotoHomepageConfig(page: Page) {
|
|
30
|
+
await page.locator('a.g-nav-link[g-target="admin"]').click();
|
|
31
|
+
await page.locator('.g-plugins-config').click();
|
|
32
|
+
const configLink = page.locator('.g-plugin-list-item[data-name="homepage"] a.g-plugin-config-link');
|
|
33
|
+
await expect(configLink).toBeVisible();
|
|
34
|
+
await configLink.click();
|
|
35
|
+
await expect(page.locator('.g-homepage-container')).toBeVisible();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Navigate back to the front page via the app title.
|
|
40
|
+
*/
|
|
41
|
+
async function gotoFrontPage(page: Page) {
|
|
42
|
+
await page.locator('.g-app-title').click();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save the homepage configuration form and wait for the success alert.
|
|
47
|
+
*/
|
|
48
|
+
async function saveHomepageConfig(page: Page) {
|
|
49
|
+
await page.locator('#g-homepage-form input[type="submit"]').click();
|
|
50
|
+
await expect(page.locator('#g-alerts-container')).toContainText('Settings saved');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test.describe('Test the homepage front-end', () => {
|
|
54
|
+
setupServer();
|
|
55
|
+
|
|
56
|
+
test('exercise homepage markdown customization', async ({ page }) => {
|
|
57
|
+
await createUser(page, 'admin', 'admin@girder.test', 'Mark', 'Down', 'password');
|
|
58
|
+
|
|
59
|
+
await gotoHomepageConfig(page);
|
|
60
|
+
|
|
61
|
+
// Write markdown and verify the preview renders it.
|
|
62
|
+
await page.locator('.g-homepage-container textarea.g-markdown-text').fill(markdown);
|
|
63
|
+
await page.locator('.g-homepage-container a.g-preview-link').click();
|
|
64
|
+
await expectMarkdown(page.locator('.g-homepage-container .g-markdown-preview'));
|
|
65
|
+
|
|
66
|
+
await saveHomepageConfig(page);
|
|
67
|
+
|
|
68
|
+
// The front page body is completely replaced by the rendered markdown.
|
|
69
|
+
await gotoFrontPage(page);
|
|
70
|
+
await expectMarkdown(page.locator('#g-app-body-container'));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('set, preview, and save welcome text and branding', async ({ page }) => {
|
|
74
|
+
await login(page, 'admin', 'password');
|
|
75
|
+
|
|
76
|
+
await gotoHomepageConfig(page);
|
|
77
|
+
|
|
78
|
+
// Clear the full-page markdown so the branding settings are used.
|
|
79
|
+
await page.locator('.g-homepage-container textarea.g-markdown-text').fill('');
|
|
80
|
+
await page.locator('.g-homepage-container a.g-preview-link').click();
|
|
81
|
+
await expect(page.locator('.g-homepage-container .g-markdown-preview')).toHaveText('Nothing to show');
|
|
82
|
+
|
|
83
|
+
// Set header, subheader, and welcome text.
|
|
84
|
+
await page.locator('#g-homepage-header').fill('Header');
|
|
85
|
+
await page.locator('#g-homepage-subheader').fill('Subheader');
|
|
86
|
+
const welcomeText = page.locator('.g-homepage-welcome-text-container textarea.g-markdown-text');
|
|
87
|
+
await welcomeText.fill(markdown);
|
|
88
|
+
await page.locator('.g-homepage-welcome-text-container a.g-preview-link').click();
|
|
89
|
+
await expectMarkdown(page.locator('.g-homepage-welcome-text-container .g-markdown-preview'));
|
|
90
|
+
|
|
91
|
+
await saveHomepageConfig(page);
|
|
92
|
+
|
|
93
|
+
// The branded front page shows the configured header/subheader and the
|
|
94
|
+
// welcome text rendered as markdown.
|
|
95
|
+
await gotoFrontPage(page);
|
|
96
|
+
await expect(page.locator('.g-frontpage-title')).toHaveText('Header');
|
|
97
|
+
await expect(page.locator('.g-frontpage-subtitle')).toHaveText('Subheader');
|
|
98
|
+
await expectMarkdown(page.locator('.g-frontpage-welcome-text-content'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('settings persist after reload and are visible anonymously', async ({ page }) => {
|
|
102
|
+
await login(page, 'admin', 'password');
|
|
103
|
+
|
|
104
|
+
// Configure the full-page markdown replacement.
|
|
105
|
+
await gotoHomepageConfig(page);
|
|
106
|
+
await page.locator('.g-homepage-container textarea.g-markdown-text').fill(markdown);
|
|
107
|
+
await saveHomepageConfig(page);
|
|
108
|
+
|
|
109
|
+
// Re-open the config page and verify the markdown was persisted.
|
|
110
|
+
await gotoHomepageConfig(page);
|
|
111
|
+
await expect(page.locator('.g-homepage-container textarea.g-markdown-text')).toHaveValue(markdown);
|
|
112
|
+
|
|
113
|
+
// Log out and verify the markdown still renders to anonymous users.
|
|
114
|
+
await logout(page);
|
|
115
|
+
await gotoFrontPage(page);
|
|
116
|
+
await expectMarkdown(page.locator('#g-app-body-container'));
|
|
117
|
+
});
|
|
118
|
+
});
|