exactdocs 0.1.0__tar.gz → 0.2.0__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.
- {exactdocs-0.1.0 → exactdocs-0.2.0}/.gitignore +8 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/PKG-INFO +10 -2
- exactdocs-0.2.0/alembic.ini +5 -0
- exactdocs-0.2.0/features/add_library.feature +55 -0
- exactdocs-0.2.0/features/add_version.feature +43 -0
- exactdocs-0.2.0/features/api.feature +17 -0
- exactdocs-0.2.0/features/articles.feature +90 -0
- exactdocs-0.2.0/features/catalog.feature +43 -0
- exactdocs-0.2.0/features/chunking.feature +24 -0
- exactdocs-0.2.0/features/chunks.feature +61 -0
- exactdocs-0.2.0/features/environment.py +25 -0
- exactdocs-0.2.0/features/fixture_repo.feature +18 -0
- exactdocs-0.2.0/features/import.feature +51 -0
- exactdocs-0.2.0/features/ingestion.feature +53 -0
- exactdocs-0.2.0/features/jobs.feature +26 -0
- exactdocs-0.2.0/features/library_readme.feature +24 -0
- exactdocs-0.2.0/features/login.feature +47 -0
- exactdocs-0.2.0/features/mcp_browse.feature +26 -0
- exactdocs-0.2.0/features/mcp_query_docs.feature +33 -0
- exactdocs-0.2.0/features/mcp_resolve.feature +29 -0
- exactdocs-0.2.0/features/mcp_search.feature +23 -0
- exactdocs-0.2.0/features/mcp_stdio.feature +19 -0
- exactdocs-0.2.0/features/private_repositories.feature +34 -0
- exactdocs-0.2.0/features/read.feature +37 -0
- exactdocs-0.2.0/features/removal.feature +42 -0
- exactdocs-0.2.0/features/repositories.feature +51 -0
- exactdocs-0.2.0/features/search.feature +35 -0
- exactdocs-0.2.0/features/serve_requires_git.feature +14 -0
- exactdocs-0.2.0/features/setup.feature +29 -0
- exactdocs-0.2.0/features/sparse_fetch.feature +12 -0
- exactdocs-0.2.0/features/steps/add_library_steps.py +134 -0
- exactdocs-0.2.0/features/steps/add_version_steps.py +70 -0
- exactdocs-0.2.0/features/steps/articles_steps.py +148 -0
- exactdocs-0.2.0/features/steps/chunking_steps.py +60 -0
- exactdocs-0.2.0/features/steps/chunks_steps.py +187 -0
- exactdocs-0.2.0/features/steps/fixture_repo_steps.py +32 -0
- exactdocs-0.2.0/features/steps/http_steps.py +209 -0
- exactdocs-0.2.0/features/steps/import_steps.py +283 -0
- exactdocs-0.2.0/features/steps/ingestion_steps.py +61 -0
- exactdocs-0.2.0/features/steps/jobs_steps.py +110 -0
- exactdocs-0.2.0/features/steps/library_readme_steps.py +49 -0
- exactdocs-0.2.0/features/steps/login_steps.py +160 -0
- exactdocs-0.2.0/features/steps/mcp_query_docs_steps.py +121 -0
- exactdocs-0.2.0/features/steps/mcp_resolve_steps.py +111 -0
- exactdocs-0.2.0/features/steps/mcp_stdio_steps.py +135 -0
- exactdocs-0.2.0/features/steps/mcp_steps.py +159 -0
- exactdocs-0.2.0/features/steps/private_repository_steps.py +139 -0
- exactdocs-0.2.0/features/steps/read_steps.py +142 -0
- exactdocs-0.2.0/features/steps/removal_steps.py +192 -0
- exactdocs-0.2.0/features/steps/repository_steps.py +59 -0
- exactdocs-0.2.0/features/steps/search_steps.py +272 -0
- exactdocs-0.2.0/features/steps/serve_requires_git_steps.py +61 -0
- exactdocs-0.2.0/features/steps/setup_steps.py +100 -0
- exactdocs-0.2.0/features/steps/sparse_fetch_steps.py +60 -0
- exactdocs-0.2.0/features/steps/sync_steps.py +129 -0
- exactdocs-0.2.0/features/steps/tokens_steps.py +158 -0
- exactdocs-0.2.0/features/sync.feature +40 -0
- exactdocs-0.2.0/features/tokens.feature +31 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/pyproject.toml +8 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app-L8dLfZA-.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.account-BBv6H3nn.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.index-Ck0Wke8Y.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.index-Dwh2_XXZ.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo-BUK4Wra-.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo-CXtxCHLH.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo_.settings-C2eefokI.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo_.settings-COV2Vl80.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries.new-BYtg6-4L.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries.new-Ck0Wke8Y.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/auth-DOqUTQT4.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/bash-BqpFWvKu.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/browse-l0znjzw4.js +2 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/c-DBVbJFpC.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/chevron-down-VvddpmDw.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/copy-kIv_spkb.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/cpp-N2d8M7Nc.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/csharp-yYt0Ikks.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/css-D8M8GYaw.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/dialog-D5mMvlm0.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/diff-BhdZizse.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/dockerfile-C0zufGsU.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/es2015-CBjFb0he.js +41 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/form-alert-DQv24T46.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/go-CYstVVJV.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/highlight-IMd1IgrZ.js +3 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/index-BMKasA_-.css +2 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/index-CsPrqFYg.js +12 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/info-BS8gDhnL.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/ini-B1CroMsf.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/java-Dde-Wnb4.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/javascript-DMgQAH-8.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/json-PmyKWHmm.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/lazyRouteComponent-C9loUZFK.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-B1kCpIQK.css +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-BsHCS9sS.js +17 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-D6FrnlqJ.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/php-DuZl5e-L.js +2 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/preload-helper-BLdHbfdN.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/python-gHmu6VPG.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/ruby-BGpZ3p4_.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/rust-BIKPVoi6.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/setup-CSE__qkX.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/sign-in-C18mr4H8.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-cyrillic-ext-wght-normal-DHTKMUil.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-cyrillic-wght-normal-B1g8a0vD.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-greek-wght-normal-BpXpdlRW.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-latin-ext-wght-normal-BVZ5su8S.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-latin-wght-normal-DMe4EKeb.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-vietnamese-wght-normal-BsaNuasd.woff2 +0 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/sql-DWRKyvJ-.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/time-CEDhx21E.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/triangle-alert-CV6_tesq.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/typescript-Da0fK7fQ.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/urls-Bj94Pjxc.css +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/urls-ghi89a1B.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/useNavigate-DuwO8yE1.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/utils-BpYXgVgn.js +2 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/xml-COl5yFSC.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/assets/yaml-DjOhhVLG.js +1 -0
- exactdocs-0.2.0/src/exactdocs/_web/index.html +27 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/__init__.py +12 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/bridges.py +448 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/cli.py +196 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/container.py +245 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/database.py +235 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/http.py +228 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/mcp.py +83 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/env.py +18 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/script.py.mako +21 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_3b8f0d6e2a71_retrieval_search.py +106 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5d1c7a93b2e4_identity_accounts.py +28 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5f3a9c21d7b4_ingestion_jobs.py +42 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5f87041a0ed2_catalog_manifests.py +84 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_7c2e91d4a6b8_retrieval_chunks.py +95 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_9b3e5a7c1d42_identity_sessions.py +34 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_a1c9e4d7b352_ingestion_library_credentials.py +25 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_a6c2d9e4b3f1_ingestion_job_chunk_counters.py +34 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_ac89e0a428cd_catalog_libraries.py +35 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_c3a8e1f94b27_ingestion_job_commit.py +21 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_c4d8e2f6a1b7_identity_api_tokens.py +38 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_d7e2a4b9c150_ingestion_job_ref_kind.py +21 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_e5b1c7a30f68_catalog_library_text_edited.py +26 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_e7a4b1c9d2f6_retrieval_chunk_id_index.py +22 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_f421064ebb87_catalog_ids_and_version_kinds.py +61 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261105_a91c4e7b3d02_retrieval_search_path.py +88 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/rest.py +186 -0
- exactdocs-0.2.0/src/exactdocs/bootstrap/settings.py +85 -0
- exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven/memory.py +283 -0
- exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven/sqlite.py +657 -0
- exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving/http.py +132 -0
- exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving/mcp.py +170 -0
- exactdocs-0.2.0/src/exactdocs/catalog/dtos.py +682 -0
- exactdocs-0.2.0/src/exactdocs/catalog/errors.py +179 -0
- exactdocs-0.2.0/src/exactdocs/catalog/models.py +370 -0
- exactdocs-0.2.0/src/exactdocs/catalog/module.py +105 -0
- exactdocs-0.2.0/src/exactdocs/catalog/ports/__init__.py +36 -0
- exactdocs-0.2.0/src/exactdocs/catalog/ports/driven.py +175 -0
- exactdocs-0.2.0/src/exactdocs/catalog/ports/driving.py +227 -0
- exactdocs-0.2.0/src/exactdocs/catalog/use_cases.py +505 -0
- exactdocs-0.2.0/src/exactdocs/identity/__init__.py +9 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/argon2id.py +28 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/memory.py +113 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/sqlite.py +280 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/__init__.py +0 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/http.py +188 -0
- exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/mcp.py +31 -0
- exactdocs-0.2.0/src/exactdocs/identity/dtos.py +112 -0
- exactdocs-0.2.0/src/exactdocs/identity/errors.py +90 -0
- exactdocs-0.2.0/src/exactdocs/identity/models.py +112 -0
- exactdocs-0.2.0/src/exactdocs/identity/module.py +89 -0
- exactdocs-0.2.0/src/exactdocs/identity/ports/__init__.py +39 -0
- exactdocs-0.2.0/src/exactdocs/identity/ports/driven.py +105 -0
- exactdocs-0.2.0/src/exactdocs/identity/ports/driving.py +114 -0
- exactdocs-0.2.0/src/exactdocs/identity/use_cases.py +332 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/__init__.py +9 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/__init__.py +1 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/__init__.py +1 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/git_cli.py +726 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/known_hosts.py +63 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/markdown_chunker.py +142 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/memory.py +168 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/secret_box.py +134 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/sqlite.py +308 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/__init__.py +1 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/http.py +247 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/worker.py +58 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/dtos.py +414 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/errors.py +335 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/models.py +1224 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/module.py +206 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/ports/__init__.py +65 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/ports/driven.py +320 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/ports/driving.py +169 -0
- exactdocs-0.2.0/src/exactdocs/ingestion/use_cases.py +1103 -0
- exactdocs-0.2.0/src/exactdocs/py.typed +0 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/__init__.py +0 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/__init__.py +0 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/memory.py +161 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/sqlite.py +363 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/sqlite_fts.py +163 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/__init__.py +0 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/http.py +66 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/mcp.py +298 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/dtos.py +478 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/errors.py +77 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/models.py +512 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/module.py +75 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/ports/__init__.py +35 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/ports/driven.py +145 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/ports/driving.py +126 -0
- exactdocs-0.2.0/src/exactdocs/retrieval/use_cases.py +492 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/tests/architecture/test_architecture.py +29 -2
- exactdocs-0.2.0/tests/architecture/test_bridge_direction.py +83 -0
- exactdocs-0.2.0/tests/bootstrap/snapshots/mcp_tools.json +966 -0
- exactdocs-0.2.0/tests/bootstrap/test_add_library_api.py +328 -0
- exactdocs-0.2.0/tests/bootstrap/test_api_v1.py +196 -0
- exactdocs-0.2.0/tests/bootstrap/test_app.py +386 -0
- exactdocs-0.2.0/tests/bootstrap/test_articles_api.py +160 -0
- exactdocs-0.2.0/tests/bootstrap/test_articles_tags_api.py +152 -0
- exactdocs-0.2.0/tests/bootstrap/test_bridges.py +176 -0
- exactdocs-0.2.0/tests/bootstrap/test_cli.py +100 -0
- exactdocs-0.2.0/tests/bootstrap/test_context7_contract.py +70 -0
- exactdocs-0.2.0/tests/bootstrap/test_context7_session_replay.py +106 -0
- exactdocs-0.2.0/tests/bootstrap/test_import.py +503 -0
- exactdocs-0.2.0/tests/bootstrap/test_jobs_api.py +153 -0
- exactdocs-0.2.0/tests/bootstrap/test_library_patch_api.py +88 -0
- exactdocs-0.2.0/tests/bootstrap/test_mcp_browse.py +357 -0
- exactdocs-0.2.0/tests/bootstrap/test_model_free.py +112 -0
- exactdocs-0.2.0/tests/bootstrap/test_no_outbound.py +537 -0
- exactdocs-0.2.0/tests/bootstrap/test_persistence.py +249 -0
- exactdocs-0.2.0/tests/bootstrap/test_private_repositories.py +303 -0
- exactdocs-0.2.0/tests/bootstrap/test_read.py +348 -0
- exactdocs-0.2.0/tests/bootstrap/test_removal_api.py +143 -0
- exactdocs-0.2.0/tests/bootstrap/test_repositories_api.py +118 -0
- exactdocs-0.2.0/tests/bootstrap/test_search_api.py +205 -0
- exactdocs-0.2.0/tests/bootstrap/test_search_migration.py +167 -0
- exactdocs-0.2.0/tests/bootstrap/test_session_api.py +278 -0
- exactdocs-0.2.0/tests/bootstrap/test_setup.py +127 -0
- exactdocs-0.2.0/tests/bootstrap/test_stdio.py +221 -0
- exactdocs-0.2.0/tests/bootstrap/test_sync_api.py +167 -0
- exactdocs-0.2.0/tests/bootstrap/test_tokens_api.py +283 -0
- exactdocs-0.2.0/tests/catalog/test_library_repository.py +229 -0
- exactdocs-0.2.0/tests/catalog/test_library_text.py +235 -0
- exactdocs-0.2.0/tests/catalog/test_manifest_repository.py +792 -0
- exactdocs-0.2.0/tests/catalog/test_manifest_use_cases.py +506 -0
- exactdocs-0.2.0/tests/catalog/test_models.py +293 -0
- exactdocs-0.2.0/tests/catalog/test_tag_selection.py +39 -0
- exactdocs-0.2.0/tests/catalog/test_use_cases.py +229 -0
- exactdocs-0.2.0/tests/conftest.py +122 -0
- exactdocs-0.2.0/tests/contracts/context7/README.md +47 -0
- exactdocs-0.2.0/tests/contracts/context7/query-docs.json +12 -0
- exactdocs-0.2.0/tests/contracts/context7/resolve-library-id.json +11 -0
- exactdocs-0.2.0/tests/contracts/context7/session.json +27 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/.gitattributes +1 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/README.md +119 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/next/docs/guide/install.md +53 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/next/docs/guide/migration.md +7 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/AGENTS.md +5 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/README.md +7 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/api/auth.md +24 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/api/client.mdx +31 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/archive/old-release.md +3 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/assets/logo.png +0 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/auth.md +25 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/callouts.md +21 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/configuration.md +20 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/setup.md +45 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/index.md +9 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/binary.md +0 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/latin1.md +12 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/undecodable.md +3 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/notes.md +3 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/reference/naming.md +19 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/api/client.mdx +38 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/guide/install.md +45 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/guide/resilience.md +38 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/reference/errors.md +12 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/expected-queries.yaml +65 -0
- exactdocs-0.2.0/tests/fixtures/docs-repo/history.toml +29 -0
- exactdocs-0.2.0/tests/fixtures/fixture_repo.py +260 -0
- exactdocs-0.2.0/tests/fixtures/heading_anchors.json +115 -0
- exactdocs-0.2.0/tests/fixtures/test_fixture_repo.py +226 -0
- exactdocs-0.2.0/tests/identity/test_account_repository.py +81 -0
- exactdocs-0.2.0/tests/identity/test_argon2id_hasher.py +43 -0
- exactdocs-0.2.0/tests/identity/test_session_repository.py +110 -0
- exactdocs-0.2.0/tests/identity/test_sessions.py +256 -0
- exactdocs-0.2.0/tests/identity/test_token_repository.py +102 -0
- exactdocs-0.2.0/tests/identity/test_tokens.py +224 -0
- exactdocs-0.2.0/tests/identity/test_use_cases.py +223 -0
- exactdocs-0.2.0/tests/ingestion/conftest.py +85 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/blank-title.md +4 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/code-first.md +5 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/empty-front-matter.md +3 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/guide/auth.md +3 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/guide/install.md +10 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/malformed.md +7 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/mdx-page.mdx +13 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/not-a-mapping.md +4 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/notes.md +1 -0
- exactdocs-0.2.0/tests/ingestion/fixtures/setext.md +8 -0
- exactdocs-0.2.0/tests/ingestion/test_add_library.py +277 -0
- exactdocs-0.2.0/tests/ingestion/test_add_version.py +208 -0
- exactdocs-0.2.0/tests/ingestion/test_check_repository.py +123 -0
- exactdocs-0.2.0/tests/ingestion/test_chunker.py +298 -0
- exactdocs-0.2.0/tests/ingestion/test_credentials.py +361 -0
- exactdocs-0.2.0/tests/ingestion/test_git_cli.py +345 -0
- exactdocs-0.2.0/tests/ingestion/test_git_credentials.py +357 -0
- exactdocs-0.2.0/tests/ingestion/test_git_fetch.py +506 -0
- exactdocs-0.2.0/tests/ingestion/test_import_version.py +802 -0
- exactdocs-0.2.0/tests/ingestion/test_job_repository.py +231 -0
- exactdocs-0.2.0/tests/ingestion/test_job_use_cases.py +267 -0
- exactdocs-0.2.0/tests/ingestion/test_job_worker.py +129 -0
- exactdocs-0.2.0/tests/ingestion/test_parse_article.py +235 -0
- exactdocs-0.2.0/tests/ingestion/test_path_selection.py +172 -0
- exactdocs-0.2.0/tests/ingestion/test_readme.py +51 -0
- exactdocs-0.2.0/tests/ingestion/test_removal.py +348 -0
- exactdocs-0.2.0/tests/ingestion/test_repository_url.py +159 -0
- exactdocs-0.2.0/tests/ingestion/test_resolve_ref.py +141 -0
- exactdocs-0.2.0/tests/ingestion/test_restart_recovery.py +104 -0
- exactdocs-0.2.0/tests/ingestion/test_secret_box.py +310 -0
- exactdocs-0.2.0/tests/ingestion/test_sync_library.py +382 -0
- exactdocs-0.2.0/tests/ingestion/test_tags.py +84 -0
- exactdocs-0.2.0/tests/retrieval/ranking/conftest.py +117 -0
- exactdocs-0.2.0/tests/retrieval/ranking/test_ranking.py +72 -0
- exactdocs-0.2.0/tests/retrieval/test_chunk_id.py +66 -0
- exactdocs-0.2.0/tests/retrieval/test_chunk_store.py +624 -0
- exactdocs-0.2.0/tests/retrieval/test_identifier_tokens.py +81 -0
- exactdocs-0.2.0/tests/retrieval/test_query_docs.py +255 -0
- exactdocs-0.2.0/tests/retrieval/test_resolve_library_id.py +315 -0
- exactdocs-0.2.0/tests/retrieval/test_search_docs.py +335 -0
- exactdocs-0.2.0/tests/retrieval/test_search_index.py +409 -0
- exactdocs-0.2.0/tests/retrieval/test_source_link.py +73 -0
- exactdocs-0.1.0/features/catalog.feature +0 -20
- exactdocs-0.1.0/features/search.feature +0 -17
- exactdocs-0.1.0/features/steps/http_steps.py +0 -56
- exactdocs-0.1.0/src/exactdocs/_web/assets/index-B350HwjP.js +0 -10
- exactdocs-0.1.0/src/exactdocs/_web/assets/index-CG8rwfz9.css +0 -2
- exactdocs-0.1.0/src/exactdocs/_web/assets/routes-CCGZxbYl.js +0 -1
- exactdocs-0.1.0/src/exactdocs/_web/index.html +0 -13
- exactdocs-0.1.0/src/exactdocs/bootstrap/__init__.py +0 -5
- exactdocs-0.1.0/src/exactdocs/bootstrap/bridges.py +0 -27
- exactdocs-0.1.0/src/exactdocs/bootstrap/cli.py +0 -48
- exactdocs-0.1.0/src/exactdocs/bootstrap/container.py +0 -28
- exactdocs-0.1.0/src/exactdocs/bootstrap/http.py +0 -90
- exactdocs-0.1.0/src/exactdocs/bootstrap/mcp.py +0 -21
- exactdocs-0.1.0/src/exactdocs/bootstrap/settings.py +0 -15
- exactdocs-0.1.0/src/exactdocs/catalog/adapters/driven/memory.py +0 -35
- exactdocs-0.1.0/src/exactdocs/catalog/adapters/driving/http.py +0 -21
- exactdocs-0.1.0/src/exactdocs/catalog/adapters/driving/mcp.py +0 -18
- exactdocs-0.1.0/src/exactdocs/catalog/dtos.py +0 -42
- exactdocs-0.1.0/src/exactdocs/catalog/errors.py +0 -16
- exactdocs-0.1.0/src/exactdocs/catalog/models.py +0 -34
- exactdocs-0.1.0/src/exactdocs/catalog/module.py +0 -27
- exactdocs-0.1.0/src/exactdocs/catalog/ports/__init__.py +0 -4
- exactdocs-0.1.0/src/exactdocs/catalog/ports/driven.py +0 -18
- exactdocs-0.1.0/src/exactdocs/catalog/ports/driving.py +0 -21
- exactdocs-0.1.0/src/exactdocs/catalog/use_cases.py +0 -33
- exactdocs-0.1.0/src/exactdocs/retrieval/adapters/driven/memory.py +0 -26
- exactdocs-0.1.0/src/exactdocs/retrieval/adapters/driving/http.py +0 -17
- exactdocs-0.1.0/src/exactdocs/retrieval/adapters/driving/mcp.py +0 -28
- exactdocs-0.1.0/src/exactdocs/retrieval/dtos.py +0 -53
- exactdocs-0.1.0/src/exactdocs/retrieval/errors.py +0 -17
- exactdocs-0.1.0/src/exactdocs/retrieval/models.py +0 -34
- exactdocs-0.1.0/src/exactdocs/retrieval/module.py +0 -18
- exactdocs-0.1.0/src/exactdocs/retrieval/ports/__init__.py +0 -4
- exactdocs-0.1.0/src/exactdocs/retrieval/ports/driven.py +0 -26
- exactdocs-0.1.0/src/exactdocs/retrieval/ports/driving.py +0 -13
- exactdocs-0.1.0/src/exactdocs/retrieval/use_cases.py +0 -22
- exactdocs-0.1.0/tests/bootstrap/test_app.py +0 -64
- exactdocs-0.1.0/tests/catalog/test_use_cases.py +0 -42
- exactdocs-0.1.0/tests/retrieval/test_search_docs.py +0 -54
- {exactdocs-0.1.0 → exactdocs-0.2.0}/README.md +0 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/src/exactdocs/__init__.py +0 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/src/exactdocs/__main__.py +0 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/src/exactdocs/_version.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/catalog/adapters → exactdocs-0.2.0/src/exactdocs/bootstrap/migrations}/__init__.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/catalog/adapters/driven → exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions}/__init__.py +0 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/src/exactdocs/catalog/__init__.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/catalog/adapters/driving → exactdocs-0.2.0/src/exactdocs/catalog/adapters}/__init__.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/retrieval/adapters → exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven}/__init__.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/retrieval/adapters/driven → exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving}/__init__.py +0 -0
- {exactdocs-0.1.0/src/exactdocs/retrieval/adapters/driving → exactdocs-0.2.0/src/exactdocs/identity/adapters}/__init__.py +0 -0
- /exactdocs-0.1.0/src/exactdocs/py.typed → /exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/__init__.py +0 -0
- {exactdocs-0.1.0 → exactdocs-0.2.0}/src/exactdocs/retrieval/__init__.py +0 -0
|
@@ -12,6 +12,9 @@ coverage.xml
|
|
|
12
12
|
htmlcov/
|
|
13
13
|
reports/
|
|
14
14
|
|
|
15
|
+
# Zensical output
|
|
16
|
+
site/
|
|
17
|
+
|
|
15
18
|
# Built web UI bundled into the wheel / image (see web/README.md)
|
|
16
19
|
packages/exactdocs/src/exactdocs/_web/
|
|
17
20
|
|
|
@@ -31,3 +34,8 @@ Thumbs.db
|
|
|
31
34
|
|
|
32
35
|
# Claude Code local settings
|
|
33
36
|
.claude/settings.local.json
|
|
37
|
+
|
|
38
|
+
# Browser end-to-end run (e2e/)
|
|
39
|
+
e2e/test-results/
|
|
40
|
+
e2e/shots/
|
|
41
|
+
e2e/node_modules/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: exactdocs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Self-hosted, version-accurate documentation for AI coding agents (MCP server).
|
|
5
5
|
Project-URL: Repository, https://github.com/SirCypkowskyy/ExactDocs
|
|
6
6
|
Project-URL: Issues, https://github.com/SirCypkowskyy/ExactDocs/issues
|
|
@@ -12,13 +12,21 @@ Classifier: Framework :: FastAPI
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Classifier: Typing :: Typed
|
|
14
14
|
Requires-Python: >=3.14
|
|
15
|
+
Requires-Dist: aiosqlite>=0.21
|
|
16
|
+
Requires-Dist: alembic>=1.16
|
|
17
|
+
Requires-Dist: argon2-cffi>=25
|
|
18
|
+
Requires-Dist: cryptography>=46
|
|
15
19
|
Requires-Dist: cyclopts>=4
|
|
16
|
-
Requires-Dist: exactdocs-kernel==0.
|
|
20
|
+
Requires-Dist: exactdocs-kernel==0.2.0
|
|
17
21
|
Requires-Dist: fastapi>=0.120
|
|
18
22
|
Requires-Dist: fastmcp>=4
|
|
23
|
+
Requires-Dist: markdown-it-py>=4
|
|
19
24
|
Requires-Dist: mcp>=2
|
|
25
|
+
Requires-Dist: platformdirs>=4
|
|
20
26
|
Requires-Dist: pydantic-settings>=2.11
|
|
21
27
|
Requires-Dist: pydantic>=2.12
|
|
28
|
+
Requires-Dist: pyyaml>=6
|
|
29
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
22
30
|
Requires-Dist: uvicorn[standard]>=0.38
|
|
23
31
|
Description-Content-Type: text/markdown
|
|
24
32
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Only for authoring revisions: just migration "what changed"
|
|
2
|
+
# (migrations run in code at startup, see exactdocs.bootstrap.database).
|
|
3
|
+
[alembic]
|
|
4
|
+
script_location = %(here)s/src/exactdocs/bootstrap/migrations
|
|
5
|
+
file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s_%%(slug)s
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Feature: Adding a library with its doc selection
|
|
2
|
+
As an admin
|
|
3
|
+
I want to create a library from a repository, choosing what to import and which ref
|
|
4
|
+
So that every import says exactly which commit it took
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running with the demo catalog
|
|
8
|
+
|
|
9
|
+
Scenario: Without a ref the default branch is imported
|
|
10
|
+
When a user adds the fixture repository with paths "docs/" and no ref
|
|
11
|
+
Then the library has a version for the default branch
|
|
12
|
+
And that version records the branch head commit SHA
|
|
13
|
+
And an import job is queued for that version
|
|
14
|
+
|
|
15
|
+
Scenario Outline: A tag, a branch or a commit is pinned to its commit
|
|
16
|
+
When a user adds the fixture repository with paths "docs/" and the ref "<ref>"
|
|
17
|
+
Then the version is "<ref>" and records the commit of "<commit>"
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
| ref | commit |
|
|
21
|
+
| v1.3.0 | v1.3.0 |
|
|
22
|
+
| next | next |
|
|
23
|
+
|
|
24
|
+
Scenario: A short commit SHA becomes the full one
|
|
25
|
+
When a user adds the fixture repository with paths "docs/" and the short SHA of "v1.4.0"
|
|
26
|
+
Then the version is the full SHA of "v1.4.0"
|
|
27
|
+
|
|
28
|
+
Scenario: An unknown ref creates nothing
|
|
29
|
+
When a user adds the fixture repository with paths "docs/" and the ref "v9.9.9"
|
|
30
|
+
Then the request fails with "unknown_ref"
|
|
31
|
+
And the library "/fixture/docs-repo" does not exist
|
|
32
|
+
|
|
33
|
+
Scenario: Exclusions are kept with the library and win over inclusions
|
|
34
|
+
When a user adds the fixture repository with paths "docs/**" and exclusion "docs/archive/**"
|
|
35
|
+
Then the library keeps the inclusion "docs/**" and the exclusion "docs/archive/**"
|
|
36
|
+
|
|
37
|
+
Scenario: Tag rules are kept with the library, normalized
|
|
38
|
+
When a user adds the fixture repository with paths "docs/**" and the tag rule "docs/api/** -> API"
|
|
39
|
+
Then the library keeps the tag rule "docs/api/** -> api"
|
|
40
|
+
|
|
41
|
+
Scenario: A tag rule that cannot work is refused
|
|
42
|
+
When a user adds the fixture repository with paths "docs/**" and the tag rule "docs/api/** -> not a tag!"
|
|
43
|
+
Then the request fails with "invalid_tag"
|
|
44
|
+
|
|
45
|
+
Scenario: A pattern that cannot work is refused
|
|
46
|
+
When a user adds the fixture repository with paths "../secrets" and no ref
|
|
47
|
+
Then the request fails with "invalid_path_pattern"
|
|
48
|
+
|
|
49
|
+
Scenario: A taken ID asks for another one
|
|
50
|
+
When a user adds the fixture repository as "/acme/payments"
|
|
51
|
+
Then the request fails with "library_id_taken"
|
|
52
|
+
|
|
53
|
+
Scenario: The picker lists the tags and branches
|
|
54
|
+
When a user lists the refs of the fixture repository
|
|
55
|
+
Then the default branch is "main", the branches are "main, next" and the tags are "v1.3.0, v1.4.0"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Feature: Adding another version to a library
|
|
2
|
+
As a team working on projects pinned to different releases
|
|
3
|
+
I want to add a version next to the ones a library has
|
|
4
|
+
So that every version is imported with exactly the same settings
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs imports the fixture docs repository
|
|
8
|
+
|
|
9
|
+
Scenario: A second version uses the library's settings
|
|
10
|
+
Given the fixture repository is imported at "v1.4.0" with paths "docs/**" excluding "docs/archive/**"
|
|
11
|
+
When the user adds the version "v1.3.0"
|
|
12
|
+
Then both "v1.3.0" and "v1.4.0" are listed
|
|
13
|
+
And "/fixture/docs-repo/v1.3.0" has the article "docs/guide/auth.md"
|
|
14
|
+
And "/fixture/docs-repo/v1.3.0" has no article "docs/archive/old-release.md"
|
|
15
|
+
|
|
16
|
+
Scenario: The answer points at the import job
|
|
17
|
+
Given "/fixture/docs-repo/v1.4.0" is imported
|
|
18
|
+
When the user adds the version "v1.3.0"
|
|
19
|
+
Then the response says the import was accepted and points to its job
|
|
20
|
+
|
|
21
|
+
Scenario: A newer release becomes the default version
|
|
22
|
+
Given "/fixture/docs-repo/v1.3.0" is imported
|
|
23
|
+
When the user adds the version "v1.4.0"
|
|
24
|
+
Then the default version is "v1.4.0"
|
|
25
|
+
|
|
26
|
+
Scenario: A branch does not take the default from a release
|
|
27
|
+
Given "/fixture/docs-repo/v1.4.0" is imported
|
|
28
|
+
When the user adds the version "next"
|
|
29
|
+
Then the default version is "v1.4.0"
|
|
30
|
+
|
|
31
|
+
Scenario: A version the library already has is refused
|
|
32
|
+
Given "/fixture/docs-repo/v1.4.0" is imported
|
|
33
|
+
When the user adds the version "v1.4.0"
|
|
34
|
+
Then adding the version fails with "version_exists"
|
|
35
|
+
|
|
36
|
+
Scenario: An unknown ref queues nothing
|
|
37
|
+
Given "/fixture/docs-repo/v1.4.0" is imported
|
|
38
|
+
When the user adds the version "v9.9.9"
|
|
39
|
+
Then adding the version fails with "unknown_ref"
|
|
40
|
+
|
|
41
|
+
Scenario: An unknown library is not found
|
|
42
|
+
When the user adds "v1.3.0" to the library "/nobody/nothing"
|
|
43
|
+
Then the library is not found
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Feature: Versioned REST API
|
|
2
|
+
As a developer of the web UI or the CLI
|
|
3
|
+
I want one documented API with stable conventions
|
|
4
|
+
So that every client behaves the same against any instance
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running with the demo catalog
|
|
8
|
+
|
|
9
|
+
Scenario: The API describes itself
|
|
10
|
+
When I fetch the OpenAPI description
|
|
11
|
+
Then every operation has a stable operation ID and documented error responses
|
|
12
|
+
|
|
13
|
+
Scenario: Errors have one shape
|
|
14
|
+
When I request the library "/vuejs/docs"
|
|
15
|
+
Then the response status is 404
|
|
16
|
+
And the error code is "not_found"
|
|
17
|
+
And the error has a message
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Feature: Version manifests
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want a version's articles to change all at once, never piece by piece
|
|
4
|
+
So that I never read half of a re-synced version
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running with the demo catalog
|
|
8
|
+
And "/acme/payments" version "main" has the active manifest at commit "aaa111":
|
|
9
|
+
| path | title | content |
|
|
10
|
+
| guide/install.md | Install | install, old |
|
|
11
|
+
| guide/auth.md | Auth | auth |
|
|
12
|
+
| api/client.md | Client | client |
|
|
13
|
+
|
|
14
|
+
Scenario: Reading an article returns the active manifest, not a pending one
|
|
15
|
+
Given "/acme/payments" version "main" has a pending manifest at commit "bbb222":
|
|
16
|
+
| path | title | content |
|
|
17
|
+
| guide/install.md | Install | install, new |
|
|
18
|
+
When I read the article "guide/install.md" of "/acme/payments" version "main"
|
|
19
|
+
Then the response status is 200
|
|
20
|
+
And the article says "install, old" at commit "aaa111"
|
|
21
|
+
|
|
22
|
+
Scenario: Activating a manifest swaps the whole version at once
|
|
23
|
+
Given "/acme/payments" version "main" has a pending manifest at commit "bbb222":
|
|
24
|
+
| path | title | content |
|
|
25
|
+
| guide/install.md | Install | install, new |
|
|
26
|
+
| guide/new.md | New | brand new |
|
|
27
|
+
When the pending manifest is activated
|
|
28
|
+
And I read the article "guide/install.md" of "/acme/payments" version "main"
|
|
29
|
+
Then the article says "install, new" at commit "bbb222"
|
|
30
|
+
When I list the articles of "/acme/payments" version "main"
|
|
31
|
+
Then the articles are "guide/install.md, guide/new.md" at commit "bbb222"
|
|
32
|
+
When I request the version "/acme/payments/main"
|
|
33
|
+
Then the version used is "main" of "/acme/payments"
|
|
34
|
+
And the commit of the version is "bbb222"
|
|
35
|
+
|
|
36
|
+
Scenario: Browsing without a ref uses the default version
|
|
37
|
+
Given "/acme/payments" version "v1.4.0" has the active manifest at commit "ccc333":
|
|
38
|
+
| path | title | content |
|
|
39
|
+
| guide/install.md | Install | install, v1.4 |
|
|
40
|
+
When I read the article "guide/install.md" of "/acme/payments" without a ref
|
|
41
|
+
Then the article says "install, v1.4" at commit "ccc333"
|
|
42
|
+
|
|
43
|
+
Scenario: Articles can be listed under a path prefix
|
|
44
|
+
When I list the articles under "guide/" of "/acme/payments" version "main"
|
|
45
|
+
Then the articles are "guide/auth.md, guide/install.md" at commit "aaa111"
|
|
46
|
+
|
|
47
|
+
Scenario: A long list is paged with a cursor
|
|
48
|
+
When I list 2 articles of "/acme/payments" version "main"
|
|
49
|
+
Then the articles are "api/client.md, guide/auth.md" at commit "aaa111"
|
|
50
|
+
And there is a next page
|
|
51
|
+
|
|
52
|
+
Scenario: An article that is not in the version is not found
|
|
53
|
+
When I read the article "guide/missing.md" of "/acme/payments" version "main"
|
|
54
|
+
Then the response status is 404
|
|
55
|
+
And the error code is "not_found"
|
|
56
|
+
|
|
57
|
+
Scenario: A version without an active manifest has no articles
|
|
58
|
+
When I read the article "guide/install.md" of "/acme/payments" version "next"
|
|
59
|
+
Then the response status is 404
|
|
60
|
+
And the error code is "not_found"
|
|
61
|
+
|
|
62
|
+
Scenario Outline: Listing filters by tags before paging
|
|
63
|
+
Given "/acme/payments" version "main" has the active manifest at commit "ddd444":
|
|
64
|
+
| path | title | content | tags |
|
|
65
|
+
| adr/0001.md | ADR | adr | adr |
|
|
66
|
+
| api/auth.md | Auth | auth | api,auth |
|
|
67
|
+
| api/pay.md | Pay | pay | api |
|
|
68
|
+
| guide/faq.md | FAQ | faq | |
|
|
69
|
+
When I filter the articles of "/acme/payments" version "main" by <match> of tags "<tags>"
|
|
70
|
+
Then the articles are "<paths>" at commit "ddd444"
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
| match | tags | paths |
|
|
74
|
+
| any | adr, API | adr/0001.md, api/auth.md, api/pay.md |
|
|
75
|
+
| all | api,auth | api/auth.md |
|
|
76
|
+
|
|
77
|
+
Scenario: A filtered list is paged over the matches only
|
|
78
|
+
Given "/acme/payments" version "main" has the active manifest at commit "ddd444":
|
|
79
|
+
| path | title | content | tags |
|
|
80
|
+
| a.md | A | a | |
|
|
81
|
+
| b.md | B | b | api |
|
|
82
|
+
| c.md | C | c | |
|
|
83
|
+
| d.md | D | d | api |
|
|
84
|
+
When I filter 1 articles of "/acme/payments" version "main" by any of tags "api"
|
|
85
|
+
Then the articles are "b.md" at commit "ddd444"
|
|
86
|
+
And there is a next page
|
|
87
|
+
|
|
88
|
+
Scenario: A filter that is not a tag is refused
|
|
89
|
+
When I filter the articles of "/acme/payments" version "main" by any of tags "not a tag!"
|
|
90
|
+
Then the response status is 422
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Feature: Library catalog
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want to address a library and a version by a predictable ID
|
|
4
|
+
So that I always get the version I asked for, never a plausible neighbour
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running with the demo catalog
|
|
8
|
+
|
|
9
|
+
Scenario: Listing indexed libraries shows each default version
|
|
10
|
+
When I list the libraries over the REST API
|
|
11
|
+
Then the response status is 200
|
|
12
|
+
And the libraries are:
|
|
13
|
+
| id | versions | default version |
|
|
14
|
+
| /acme/ledger | main | main |
|
|
15
|
+
| /acme/payments | main, next, v1.3.0, v1.4.0 | v1.4.0 |
|
|
16
|
+
|
|
17
|
+
Scenario: A library ID without a ref means its default version
|
|
18
|
+
When I request the library "/acme/payments"
|
|
19
|
+
Then the response status is 200
|
|
20
|
+
And the default version of "/acme/payments" is "v1.4.0"
|
|
21
|
+
|
|
22
|
+
Scenario: The library ID is not case-sensitive
|
|
23
|
+
When I request the library "/Acme/Payments"
|
|
24
|
+
Then the default version of "/acme/payments" is "v1.4.0"
|
|
25
|
+
|
|
26
|
+
Scenario: A ref names exactly that version
|
|
27
|
+
When I request the version "/acme/payments/v1.3.0"
|
|
28
|
+
Then the version used is "v1.3.0" of "/acme/payments"
|
|
29
|
+
|
|
30
|
+
Scenario: Refs are case-sensitive and never approximated
|
|
31
|
+
When I request the version "/acme/payments/V1.3.0"
|
|
32
|
+
Then the response status is 404
|
|
33
|
+
And the error code is "not_found"
|
|
34
|
+
|
|
35
|
+
Scenario: A malformed ID is rejected
|
|
36
|
+
When I request the library "/acme/pay$ments"
|
|
37
|
+
Then the response status is 422
|
|
38
|
+
And the error code is "validation_error"
|
|
39
|
+
|
|
40
|
+
Scenario: Asking for a library that is not indexed
|
|
41
|
+
When I request the library "/vuejs/docs"
|
|
42
|
+
Then the response status is 404
|
|
43
|
+
And the error code is "not_found"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Feature: Heading-based chunks
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want articles cut at their headings and never inside a code block or table
|
|
4
|
+
So that a search result is a section I can use as it is
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given the fixture docs repository is built and served
|
|
8
|
+
|
|
9
|
+
Scenario: A section is addressed by its heading path
|
|
10
|
+
When I chunk "docs/guide/install.md" of version "v1.4.0"
|
|
11
|
+
Then there is a chunk with the heading path "Guide > Install > Docker"
|
|
12
|
+
And that chunk keeps its fenced "bash" block whole
|
|
13
|
+
|
|
14
|
+
Scenario: The install table stays in one chunk
|
|
15
|
+
When I chunk "docs/guide/install.md" of version "v1.4.0"
|
|
16
|
+
Then the chunk "Guide > Install" holds the whole platform table
|
|
17
|
+
|
|
18
|
+
Scenario: Repeated headings are told apart
|
|
19
|
+
When I chunk "docs/guide/install.md" of version "v1.4.0"
|
|
20
|
+
Then the chunks "Guide > Install > Docker > Troubleshooting" and "Guide > Install > From source > Troubleshooting" have anchors "troubleshooting" and "troubleshooting-1"
|
|
21
|
+
|
|
22
|
+
Scenario: A parser change re-chunks
|
|
23
|
+
Given the parser version is bumped
|
|
24
|
+
Then the chunking-config hash differs from before
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Feature: Version-scoped chunk IDs
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want a chunk ID to name exactly one chunk of one version
|
|
4
|
+
So that read_chunk is unambiguous and an ID survives a re-sync when its section did not change
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running with the demo catalog
|
|
8
|
+
|
|
9
|
+
Scenario: An unchanged article keeps its chunk IDs when the branch is re-synced
|
|
10
|
+
Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
|
|
11
|
+
| path | text |
|
|
12
|
+
| guide/auth.md | auth |
|
|
13
|
+
| guide/install.md | install v1 |
|
|
14
|
+
When "/acme/payments" version "main" is indexed as "second" at commit "bbb222":
|
|
15
|
+
| path | text |
|
|
16
|
+
| guide/auth.md | auth |
|
|
17
|
+
| guide/install.md | install v2 |
|
|
18
|
+
Then every chunk of "guide/auth.md" has the same ID in "first" and "second"
|
|
19
|
+
And no chunk of "guide/install.md" has the same ID in "first" and "second"
|
|
20
|
+
|
|
21
|
+
Scenario: The same content in two versions is stored once under different chunk IDs
|
|
22
|
+
Given "/acme/payments" version "v1.3.0" is indexed as "old" at commit "aaa111":
|
|
23
|
+
| path | text |
|
|
24
|
+
| guide/install.md | install |
|
|
25
|
+
When "/acme/payments" version "v1.4.0" is indexed as "new" at commit "bbb222":
|
|
26
|
+
| path | text |
|
|
27
|
+
| guide/install.md | install |
|
|
28
|
+
Then "new" stored no chunk set of its own
|
|
29
|
+
And no chunk of "guide/install.md" has the same ID in "old" and "new"
|
|
30
|
+
|
|
31
|
+
Scenario: Indexing the same manifest twice stores nothing new
|
|
32
|
+
Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
|
|
33
|
+
| path | text |
|
|
34
|
+
| guide/install.md | install |
|
|
35
|
+
When the manifest "first" is indexed again
|
|
36
|
+
Then nothing new was stored
|
|
37
|
+
And every chunk of "guide/install.md" has the same ID in "first" and "first again"
|
|
38
|
+
|
|
39
|
+
Scenario: A chunk can be read only once its manifest is active
|
|
40
|
+
Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
|
|
41
|
+
| path | text |
|
|
42
|
+
| guide/install.md | install |
|
|
43
|
+
When I read the second chunk of "guide/install.md" in "first"
|
|
44
|
+
Then the chunk cannot be found
|
|
45
|
+
When the manifest "first" is activated
|
|
46
|
+
And I read the second chunk of "guide/install.md" in "first"
|
|
47
|
+
Then the chunk says "install two" and its previous chunk is the first one
|
|
48
|
+
|
|
49
|
+
Scenario: A chunk ID from a superseded manifest says it is stale
|
|
50
|
+
Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
|
|
51
|
+
| path | text |
|
|
52
|
+
| guide/install.md | install v1 |
|
|
53
|
+
When the manifest "first" is activated
|
|
54
|
+
And "/acme/payments" version "main" is indexed as "second" at commit "bbb222":
|
|
55
|
+
| path | text |
|
|
56
|
+
| guide/install.md | install v2 |
|
|
57
|
+
When the manifest "second" is activated
|
|
58
|
+
And I read the second chunk of "guide/install.md" in "first"
|
|
59
|
+
Then the chunk is stale and says how to find the current one
|
|
60
|
+
When I read the second chunk of "guide/install.md" in "second"
|
|
61
|
+
Then the chunk says "install v2 two" and its previous chunk is the first one
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Behave hooks: build the fixture docs repository once per run and serve it on 127.0.0.1."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
from contextlib import ExitStack
|
|
6
|
+
from importlib import import_module
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from behave.runner import Context
|
|
10
|
+
|
|
11
|
+
# The fixture builder is test support shared with pytest, not part of the package. behave has no
|
|
12
|
+
# pythonpath option; import_module keeps ruff (E402) and ty from needing the path at lint time.
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parents[1] / "tests" / "fixtures"))
|
|
14
|
+
fixture_repo = import_module("fixture_repo")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def before_all(context: Context) -> None:
|
|
18
|
+
context.stack = ExitStack()
|
|
19
|
+
base = Path(context.stack.enter_context(tempfile.TemporaryDirectory(prefix="exactdocs-fx-")))
|
|
20
|
+
context.fixture_repo = context.stack.enter_context(fixture_repo.served(base))
|
|
21
|
+
context.fixture_git = fixture_repo.git
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def after_all(context: Context) -> None:
|
|
25
|
+
context.stack.close()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Feature: A versioned fixture docs repository
|
|
2
|
+
Import and search are asserted against one stable git repository, served over both transports
|
|
3
|
+
that the importer supports.
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given the fixture docs repository is built and served
|
|
7
|
+
|
|
8
|
+
Scenario Outline: Both git servers advertise the same versions
|
|
9
|
+
When I list the remote refs of the fixture repository over <transport>
|
|
10
|
+
Then the remote has the tags "v1.3.0, v1.4.0" and the branch "next"
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
| transport |
|
|
14
|
+
| file |
|
|
15
|
+
| http |
|
|
16
|
+
|
|
17
|
+
Scenario: An identifier exists in one version only
|
|
18
|
+
Then "retry_policy" occurs in version "v1.4.0" but not in version "v1.3.0"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Feature: An import publishes a complete version or nothing
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want a version to appear all at once, with the commit it was imported from
|
|
4
|
+
So that I never read half of a version, and a failed import never hides what was served before
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs imports the fixture docs repository
|
|
8
|
+
|
|
9
|
+
Scenario: An import publishes the whole version at once
|
|
10
|
+
When "docs/" of the fixture repository at "v1.3.0" is imported
|
|
11
|
+
Then the import succeeds and reports every file as processed
|
|
12
|
+
And "/fixture/docs-repo/v1.3.0" lists every selected article
|
|
13
|
+
And every article and chunk carries the commit SHA of "v1.3.0"
|
|
14
|
+
|
|
15
|
+
Scenario: One bad file does not fail the import
|
|
16
|
+
When "docs/" of the fixture repository at "v1.3.0" is imported
|
|
17
|
+
Then the import succeeds
|
|
18
|
+
And the job lists these skipped files:
|
|
19
|
+
| path | code | reason |
|
|
20
|
+
| docs/legacy/binary.md | binary | binary file |
|
|
21
|
+
| docs/legacy/undecodable.md | undecodable | not text in any supported encoding |
|
|
22
|
+
| docs/reference/oversized.md | too_large | larger than 2097152 bytes |
|
|
23
|
+
|
|
24
|
+
Scenario: A Latin-1 file is decoded, not skipped
|
|
25
|
+
When "docs/" of the fixture repository at "v1.3.0" is imported
|
|
26
|
+
Then the article "docs/legacy/latin1.md" of "/fixture/docs-repo/v1.3.0" keeps its accented characters
|
|
27
|
+
|
|
28
|
+
Scenario: A failed import leaves the previous state served
|
|
29
|
+
Given "/fixture/docs-repo/next" is imported
|
|
30
|
+
When a re-import of "next" fails before activation
|
|
31
|
+
Then "/fixture/docs-repo/next" still serves the previous commit
|
|
32
|
+
|
|
33
|
+
Scenario: Versions stay apart
|
|
34
|
+
Given "/fixture/docs-repo/v1.3.0" is imported
|
|
35
|
+
When "docs/" of the fixture repository at "v1.4.0" is imported
|
|
36
|
+
Then "/fixture/docs-repo/v1.4.0" has the article "docs/guide/resilience.md"
|
|
37
|
+
And "/fixture/docs-repo/v1.3.0" has no article "docs/guide/resilience.md"
|
|
38
|
+
|
|
39
|
+
Scenario: Re-importing a commit changes nothing
|
|
40
|
+
Given "/fixture/docs-repo/v1.3.0" is imported
|
|
41
|
+
When "v1.3.0" is imported again
|
|
42
|
+
Then "/fixture/docs-repo/v1.3.0" lists the same articles and chunks as before
|
|
43
|
+
|
|
44
|
+
Scenario: Exclusions win over inclusions
|
|
45
|
+
When "docs/**" of the fixture repository at "v1.3.0" is imported excluding "docs/archive/**"
|
|
46
|
+
Then "/fixture/docs-repo/v1.3.0" has no article "docs/archive/old-release.md"
|
|
47
|
+
And "/fixture/docs-repo/v1.3.0" has the article "docs/guide/auth.md"
|
|
48
|
+
|
|
49
|
+
Scenario: Front-matter and path-rule tags combine
|
|
50
|
+
When "docs/**" of the fixture repository at "v1.3.0" is imported with the tag rule "docs/api/** -> api"
|
|
51
|
+
Then the article "docs/api/auth.md" of "/fixture/docs-repo/v1.3.0" is tagged "api" and "auth"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Feature: Safe article parsing
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want article titles to be right and hidden instructions to be gone
|
|
4
|
+
So that I can trust what I read from a doc repository
|
|
5
|
+
|
|
6
|
+
Scenario: The article title follows front matter, then H1, then file name
|
|
7
|
+
Given the article "guide/install.md":
|
|
8
|
+
"""
|
|
9
|
+
---
|
|
10
|
+
title: Install ExactDocs
|
|
11
|
+
---
|
|
12
|
+
# Getting started
|
|
13
|
+
"""
|
|
14
|
+
And the article "guide/auth.md":
|
|
15
|
+
"""
|
|
16
|
+
# Authentication
|
|
17
|
+
"""
|
|
18
|
+
And the article "notes.md":
|
|
19
|
+
"""
|
|
20
|
+
Just some notes.
|
|
21
|
+
"""
|
|
22
|
+
When the articles are imported
|
|
23
|
+
Then the title of "guide/install.md" is "Install ExactDocs"
|
|
24
|
+
And the title of "guide/auth.md" is "Authentication"
|
|
25
|
+
And the title of "notes.md" is "notes"
|
|
26
|
+
|
|
27
|
+
Scenario: Hidden characters are removed on import
|
|
28
|
+
Given the article "hidden.md" contains a zero-width space and a bidi override
|
|
29
|
+
When the articles are imported
|
|
30
|
+
Then the stored Markdown of "hidden.md" contains neither
|
|
31
|
+
And the stored Markdown of "hidden.md" is otherwise identical to the source
|
|
32
|
+
|
|
33
|
+
Scenario: Malformed front matter does not stop the import
|
|
34
|
+
Given the article "broken.md":
|
|
35
|
+
"""
|
|
36
|
+
---
|
|
37
|
+
title: [unclosed
|
|
38
|
+
---
|
|
39
|
+
# Still here
|
|
40
|
+
"""
|
|
41
|
+
When the articles are imported
|
|
42
|
+
Then the title of "broken.md" is "Still here"
|
|
43
|
+
And "broken.md" is listed with the problem "malformed front matter"
|
|
44
|
+
|
|
45
|
+
Scenario: MDX components are kept as text
|
|
46
|
+
Given the article "tabs.mdx":
|
|
47
|
+
"""
|
|
48
|
+
import { Tabs } from '@site/components'
|
|
49
|
+
|
|
50
|
+
<Tabs>npm i exactdocs</Tabs>
|
|
51
|
+
"""
|
|
52
|
+
When the articles are imported
|
|
53
|
+
Then the stored Markdown of "tabs.mdx" is identical to the source
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Feature: Background jobs
|
|
2
|
+
As someone importing a large documentation repository
|
|
3
|
+
I want long tasks to run in the background and show how far they got
|
|
4
|
+
So that a request never blocks and I can tell a slow import from a stuck one
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs is running
|
|
8
|
+
|
|
9
|
+
Scenario: Seeing how far a job got
|
|
10
|
+
Given a job over 10 files of "/acme/payments" that has finished 4
|
|
11
|
+
When I request that job
|
|
12
|
+
Then the response status is 200
|
|
13
|
+
And the job is running with 4 of 10 files done
|
|
14
|
+
|
|
15
|
+
Scenario: Listing the jobs of a library, newest first
|
|
16
|
+
Given a finished job "job-1" of "/acme/payments"
|
|
17
|
+
And a finished job "job-2" of "/acme/payments"
|
|
18
|
+
And a finished job "job-3" of "/acme/ledger"
|
|
19
|
+
When I list the jobs of the library "/acme/payments"
|
|
20
|
+
Then the response status is 200
|
|
21
|
+
And the jobs are, newest first: "job-2", "job-1"
|
|
22
|
+
|
|
23
|
+
Scenario: Asking for a job that does not exist
|
|
24
|
+
When I request the job "no-such-job"
|
|
25
|
+
Then the response status is 404
|
|
26
|
+
And the error code is "not_found"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Feature: A library gets its title and description from the root README
|
|
2
|
+
As an AI coding agent
|
|
3
|
+
I want libraries to carry a meaningful title and description
|
|
4
|
+
So that I can pick the right library in "resolve-library-id" without anyone typing them
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given ExactDocs imports the fixture docs repository
|
|
8
|
+
|
|
9
|
+
Scenario: Defaults come from the root README even outside the selection
|
|
10
|
+
When "docs/" of the fixture repository at "v1.3.0" is imported
|
|
11
|
+
Then the title of the library is the first heading of the README
|
|
12
|
+
And the description of the library is the first paragraph of the README
|
|
13
|
+
|
|
14
|
+
Scenario: The user can edit them
|
|
15
|
+
Given "/fixture/docs-repo/v1.3.0" is imported
|
|
16
|
+
When the user changes the title to "Payments SDK"
|
|
17
|
+
Then "list_libraries" shows the title "Payments SDK"
|
|
18
|
+
|
|
19
|
+
Scenario: An edited title survives a re-import
|
|
20
|
+
Given "/fixture/docs-repo/v1.3.0" is imported
|
|
21
|
+
And the user changes the title to "Payments SDK"
|
|
22
|
+
When "v1.3.0" is imported again
|
|
23
|
+
Then the title of the library is "Payments SDK"
|
|
24
|
+
And the description of the library is the first paragraph of the README
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Feature: Admin session
|
|
2
|
+
As the admin of an instance
|
|
3
|
+
I want to sign in to the web UI with a session that expires and cannot be stolen by script
|
|
4
|
+
So that only I can use the instance from a browser
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given a fresh instance
|
|
8
|
+
And the admin "ada" was set up with the password "correct horse battery staple"
|
|
9
|
+
|
|
10
|
+
Scenario: Signing in and out
|
|
11
|
+
Then the libraries are not reachable
|
|
12
|
+
When the admin signs in with the password "correct horse battery staple"
|
|
13
|
+
Then the UI session is active for "ada"
|
|
14
|
+
And the session cookie is HttpOnly and SameSite=Lax
|
|
15
|
+
And the libraries are reachable
|
|
16
|
+
When the admin signs out
|
|
17
|
+
Then the libraries are no longer reachable with that session
|
|
18
|
+
And the UI session is not active
|
|
19
|
+
|
|
20
|
+
Scenario: A wrong password and an unknown user are told apart by nobody
|
|
21
|
+
When the admin signs in with the password "wrong horse battery staple"
|
|
22
|
+
Then the sign-in is refused with "invalid_credentials"
|
|
23
|
+
When "nobody" signs in with the password "correct horse battery staple"
|
|
24
|
+
Then the sign-in is refused with "invalid_credentials"
|
|
25
|
+
And no session cookie was set
|
|
26
|
+
|
|
27
|
+
Scenario: A session cookie never authenticates MCP
|
|
28
|
+
Given the admin is signed in to the UI
|
|
29
|
+
When the browser calls the MCP endpoint with the session cookie only
|
|
30
|
+
Then the MCP endpoint treats the call exactly like one without the cookie
|
|
31
|
+
|
|
32
|
+
Scenario: Another site cannot sign the admin in or out
|
|
33
|
+
Given the admin is signed in to the UI
|
|
34
|
+
When a page on "https://evil.example" signs the admin out
|
|
35
|
+
Then the request is forbidden
|
|
36
|
+
And the libraries are reachable
|
|
37
|
+
When a page on "https://evil.example" signs in with the password "correct horse battery staple"
|
|
38
|
+
Then the request is forbidden
|
|
39
|
+
|
|
40
|
+
Scenario: Resetting the password signs the admin out everywhere
|
|
41
|
+
Given the admin is signed in to the UI
|
|
42
|
+
When the operator runs "exactdocs admin reset-password" with the username "ada" and the password "a brand new passphrase"
|
|
43
|
+
Then the libraries are no longer reachable with that session
|
|
44
|
+
When the admin signs in with the password "correct horse battery staple"
|
|
45
|
+
Then the sign-in is refused with "invalid_credentials"
|
|
46
|
+
When the admin signs in with the password "a brand new passphrase"
|
|
47
|
+
Then the UI session is active for "ada"
|