From 5c33d45112eac2e8674a7e19c58124d1bd1741b3 Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Thu, 13 Nov 2025 23:56:05 +0000 Subject: [PATCH] Update Readme Files --- AGENTS.md | 175 +++--------------- __pycache__/salt.cpython-312.pyc | Bin 5042 -> 5423 bytes __pycache__/salt2.cpython-312.pyc | Bin 0 -> 3663 bytes salt.py | 15 +- salt2.py | 36 +++- .../test_cli2.cpython-312-pytest-9.0.1.pyc | Bin 0 -> 3295 bytes .../test_hashing.cpython-312-pytest-9.0.1.pyc | Bin 6549 -> 9290 bytes ...t_integration.cpython-312-pytest-9.0.1.pyc | Bin 0 -> 7650 bytes tests/test_cli2.py | 18 ++ tests/test_hashing.py | 13 ++ tests/test_integration.py | 51 +++++ 11 files changed, 155 insertions(+), 153 deletions(-) create mode 100644 __pycache__/salt2.cpython-312.pyc create mode 100644 tests/__pycache__/test_cli2.cpython-312-pytest-9.0.1.pyc create mode 100644 tests/__pycache__/test_integration.cpython-312-pytest-9.0.1.pyc create mode 100644 tests/test_cli2.py create mode 100644 tests/test_integration.py diff --git a/AGENTS.md b/AGENTS.md index b905553..8a9cb40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Overview -This is a Python password hashing utility supporting multiple cryptographic algorithms (PBKDF2, Argon2, bcrypt). The project provides both library functions and two CLI implementations with different UX patterns. +This is a Python password hashing utility supporting multiple cryptographic algorithms (PBK-DF2, Argon2, bcrypt). The project provides both library functions and two CLI implementations with different UX patterns. **Key Architecture:** - **Algorithm Registry Pattern**: Pluggable hash algorithms implementing a common `Algorithm` protocol @@ -21,7 +21,9 @@ bcrypt_algorithm.py # bcrypt implementation tests/ test_hashing.py # Tests for core hash/verify functions test_cli.py # Tests for salt.py CLI behavior + test_cli2.py # Tests for salt2.py CLI behavior test_algorithms.py # Tests for algorithm implementations + test_integration.py # End-to-end tests for CLI tools ``` ## Essential Commands @@ -36,21 +38,23 @@ python3 salt2.py generate "MyPassword" # Alternative CLI # Hash with specific algorithm python3 salt.py hash --algorithm argon2 "MyPassword" -python3 salt.py --algorithm bcrypt "MyPassword" # Shortcut with algorithm -python3 salt2.py generate "MyPassword" # Note: salt2.py doesn't support --algorithm yet +python3 salt.py --algorithm bcrypt "MyPassword" # Shortcut with algorithm +python3 salt2.py generate --algorithm argon2 "MyPassword" # Verify a password python3 salt.py verify "MyPassword" python3 salt.py verify --algorithm argon2 "MyPassword" "" +python3 salt2.py verify --algorithm bcrypt "MyPassword" "" # List available algorithms -python3 salt.py list-algorithms # Not implemented yet, would be useful addition +python3 salt.py list-algorithms +python3 salt2.py list-algorithms ``` ### Testing ```bash -# Run all tests (20 tests total as of now) +# Run all tests (28 tests total as of now) python3 -m pytest # Verbose output @@ -60,6 +64,7 @@ python3 -m pytest -v python3 -m pytest tests/test_hashing.py python3 -m pytest tests/test_cli.py python3 -m pytest tests/test_algorithms.py +python3 -m pytest tests/test_integration.py # Run specific test patterns python3 -m pytest -k verify @@ -155,7 +160,7 @@ python3 salt.py --algorithm argon2 "mypassword" ``` **Normalization rules:** -1. If first arg is `hash` or `verify` → pass through unchanged +1. If first arg is `hash`, `verify`, or `list-algorithms` → pass through unchanged 2. If first arg is `-h` or `--help` → pass through unchanged 3. If first arg is `--algorithm` or `-a` → prepend `hash` 4. Otherwise (plain password) → prepend `hash` @@ -216,7 +221,9 @@ DEFAULT_ITERATIONS = int(os.environ.get("PBKDF2_ITERATIONS", "200000")) **Test organization:** - `test_hashing.py`: Core `hash_password()` and `verify_password()` functions - `test_cli.py`: CLI behavior for `salt.py` (including shortcut syntax) +- `test_cli2.py`: CLI behavior for `salt2.py` - `test_algorithms.py`: Individual algorithm implementations +- `test_integration.py`: End-to-end integration tests for CLI commands ### Testing Patterns @@ -246,8 +253,8 @@ Exit codes: 0 = success, 1 = verification failure ### Test Execution Notes -- 20 tests total (as of current state) -- Test execution time: ~5 seconds (mostly from Argon2/bcrypt hashing) +- 28 tests total (as of current state) +- Test execution time: ~7 seconds (mostly from Argon2/bcrypt hashing) - All tests must pass before committing - Use `pytest -v` for verbose output - Use `pytest -k ` to run subset @@ -270,7 +277,7 @@ def test__algorithm_identifier(): assert algo.identifier == "" ``` -And to `tests/test_cli.py`: +And to `tests/test_cli.py` or `tests/test_cli2.py`: ```python def test_main_hash_with_algorithm_(): """Test hash command with --algorithm .""" @@ -285,7 +292,7 @@ The imports at the bottom of `algorithms.py` trigger registration. If you import ### 2. CLI Output to Stdout (Tests Must Capture) -The CLI functions print to stdout. Tests use `main([...])` to check exit codes but don't capture output. If you need to test output content, use `capsys` fixture: +The CLI functions print to stdout. Tests use `main([...])` to check exit codes but don't capture output. If you need to test output content, use `capsys` fixture. Subprocess-based integration tests also capture stdout. ```python def test_output_format(capsys): @@ -315,21 +322,11 @@ DEFAULT_ITERATIONS = int(os.environ.get("PBKDF2_ITERATIONS", "200000")) CLI output and help text are in German: - `"✓ Passwort korrekt"` / `"✗ Passwort falsch"` - Help text uses German descriptions -- Comments in code are mixed German/English - When adding CLI features, maintain German for user-facing text ### 6. Exception Handling in Verify -`verify_password()` catches exceptions and returns `False` rather than raising: -```python -try: - salt = base64.b64decode(salt_b64, validate=True) - stored_hash = base64.b64decode(hash_b64, validate=True) -except (binascii.Error, ValueError): - return False -``` - -This is intentional for security (avoid leaking information via exception types). +`verify_password()` and algorithm implementations catch exceptions and return `False` rather than raising. This is intentional for security to avoid leaking information via exception types. ### 7. Dynamic Algorithm List @@ -346,15 +343,7 @@ hash_parser.add_argument( This means adding a new algorithm automatically adds it to CLI choices. -### 8. salt2.py Missing Algorithm Support - -**Current limitation:** `salt2.py` does NOT yet support the `--algorithm` flag. It only uses the default (PBKDF2). To add support: -1. Update `_build_parser()` to add `--algorithm` argument to both subparsers -2. Update `_command_generate()` to pass `algorithm=args.algorithm` -3. Update `_command_verify()` to pass `algorithm=args.algorithm` -4. Add tests to `tests/test_cli.py` or create `tests/test_cli2.py` - -### 9. Relative Imports vs Absolute Imports +### 8. Relative Imports vs Absolute Imports The code uses **absolute imports** (not relative): ```python @@ -367,7 +356,6 @@ This works because modules are in the root directory, not a package. Keep this p ### Cryptographic Randomness - Always use `os.urandom()` for salt generation (never `random` module) -- Never use predictable values or timestamps ### Timing Attack Protection - Use `hmac.compare_digest()` for hash comparison (PBKDF2) @@ -381,12 +369,10 @@ This works because modules are in the root directory, not a package. Keep this p ### Iteration Count - Default 200,000 for PBKDF2 (OWASP recommended as of 2023) - Configurable via `PBKDF2_ITERATIONS` environment variable -- Always parameterize in functions (don't hardcode) ### No Logging of Secrets - Never log passwords, salts, or hashes - Print statements only in CLI code for user output -- Tests don't need to capture sensitive values ## Configuration and Environment @@ -394,7 +380,7 @@ This works because modules are in the root directory, not a package. Keep this p **`PBKDF2_ITERATIONS`** - Default: `"200000"` (as string, converted to int) -- Used by: `pbkdf2_algorithm.py` and `salt.py` +- Used by: `pbkdf2_algorithm.py` - Example: `export PBKDF2_ITERATIONS=300000` ### Dependencies (requirements.txt) @@ -405,149 +391,38 @@ argon2-cffi>=23.1.0 # Argon2id implementation bcrypt>=4.1.0 # bcrypt implementation ``` -**Standard library only:** `hashlib`, `hmac`, `os`, `base64`, `binascii`, `argparse`, `sys` - ### Python Version Requirements - **Minimum:** Python 3.11 (for `|` union type syntax) - **Tested on:** Python 3.12.3 -- **Type hints:** Using modern syntax (`int | None`, not `Optional[int]`) ## Commit Guidelines ### Commit Message Format Use conventional commit format with imperative mood: - ``` : - - - - -``` - -**Types in use:** -- `feat:` - New features (e.g., "feat: add argon2 algorithm") -- `fix:` - Bug fixes -- `test:` - Test additions/changes -- `refactor:` - Code restructuring without behavior change -- `docs:` - Documentation updates - -### Examples from Git History - -``` -docs: add README.md for repository -docs: translate CLAUDE.md to German -first commit ``` +**Types in use:** `feat`, `fix`, `test`, `refactor`, `docs`. ### Before Committing 1. Run full test suite: `python3 -m pytest -v` -2. Ensure all tests pass (20/20) -3. Verify no regressions in existing functionality -4. Check that new code follows style conventions -5. Add tests for new functionality - -### Pull Request Guidelines - -- Keep PRs focused on single feature/fix -- Include motivation in description -- Reference issues with `Fixes #ID` -- For security changes: include CLI output or reproduction -- All tests must pass -- Maintain >90% coverage - -## Planned Enhancements and Known Gaps - -Based on existing documentation and code: - -### Missing Features (mentioned in plans but not implemented) - -1. **`list-algorithms` command** - Mentioned in plan (Task 9) but not in current code -2. **salt2.py algorithm support** - `salt2.py` doesn't support `--algorithm` flag yet -3. **Integration tests** - `tests/test_integration.py` mentioned in plan but not created -4. **Backward compatibility test** - Specific test mentioned in Task 12 plan - -### Documentation Gaps - -1. README.md is in German but doesn't mention multi-algorithm support yet -2. CLAUDE.md is German translation but may be outdated vs English version -3. No API docs for algorithm developers - -### Potential Improvements - -1. Add `list-algorithms` subcommand to CLIs -2. Complete algorithm support in `salt2.py` -3. Add integration tests with subprocess -4. Document algorithm addition process more clearly -5. Consider packaging as proper Python package (`salt/` directory structure) +2. Ensure all tests pass (28/28) +3. Verify no regressions +4. Add tests for new functionality ## Working with Plans and Documentation ### Plan Execution Pattern -The repository includes a detailed plan at `docs/plans/2025-11-13-multi-algorithm-support.md`: -- 12 tasks with step-by-step TDD approach -- Each task has verification steps -- Plan includes commit message examples -- Some tasks completed, others not yet - -**If implementing from plans:** -1. Read the full task before starting -2. Follow TDD: failing test first, then implementation -3. Run tests after each step -4. Use suggested commit messages -5. Mark off checklist items as you complete them +The repository includes a detailed plan at `docs/plans/2025-11-13-multi-algorithm-support.md`. When implementing from plans, follow the TDD approach outlined in the tasks. ### Multiple Documentation Files - - **AGENTS.md**: This file - agent/developer guidelines - **CLAUDE.md**: German translation, more detailed code examples - **README.md**: User-facing documentation in German - **docs/plans/*.md**: Implementation plans with TDD steps Keep these in sync when making significant changes. - -## Quick Reference - -### File a Bug or Add a Feature - -1. Create test demonstrating issue/feature -2. Implement fix/feature -3. Run `python3 -m pytest -v` -4. Commit with conventional message -5. Update docs if needed - -### Debug a Test Failure - -```bash -# Run specific test with verbose output -python3 -m pytest tests/test_file.py::test_name -vv - -# Run with pdb on failure -python3 -m pytest --pdb tests/test_file.py::test_name - -# See print statements -python3 -m pytest -s tests/test_file.py -``` - -### Verify All Systems - -```bash -# Comprehensive check -python3 -m pytest -v && \ -python3 salt.py "test" && \ -python3 salt.py hash --algorithm argon2 "test" && \ -python3 salt.py hash --algorithm bcrypt "test" && \ -python3 salt2.py generate "test" && \ -echo "All systems operational" -``` - ---- - -**Document Version:** 2025-11-13 -**Last Updated:** Initial comprehensive analysis based on repository state -**Test Count:** 20 tests (all passing) -**Python Version:** 3.12.3 diff --git a/__pycache__/salt.cpython-312.pyc b/__pycache__/salt.cpython-312.pyc index 8132b80da24f29d650cb253525619c2d5bb82508..50330811d4d6252f029ddb664f7c7d5b7485fde7 100644 GIT binary patch delta 1214 zcmZuwO>7fK6rS0gwbyH}|LTy~abQCT$yPXt6w$OO1w{#jwvs`B5KV(tOT5@7&N_X& zMoQ!mapTYfXf8+yAtWvkBt)X#dPAIg38WQZfz%2(M4}=p^w1ot&a6#iq>i-TzWv^t z_x8>EX1V9yr{SMfRY7e1V{a?2Mem1S5FsV3{6XGfs`$30leyI0;F7>25W=V`97lS4 z7C3Qx<&AF=<4*dOOuyNTl!}g0iOU(ihD9 zmiC9*$S;aHTW@fnw4IV znj(Ew=^DB&Y{mZ&CuolDSK9*q>0b5+N8A>AQ{7Ig;<+|@QEkB~j-%;D%BI&{d5P3N zA=FeMu$6mGY~mIAR_zJ~r-xXPSQVe!LGMe6&{nY9$?Gn%y8mCRG4e_FUhpmWn=-3a z-^V>Q`NCEI$G$mHk7U>Rae2e=gaf={rH#SKjxAMaO?ucjPc$u~(TCw~`Zk8MoKh@qwT>OmAYN)Q%6}33?;`s(&|*zdME_ nxc&XE6jnF(AriVY|5!{uQxaEB(35LQdX=QvjVCE+VKx5+k`EGU delta 763 zcmY*XO=}Zj5Z*W0O|rYmZnB%8Nuy0QA#6d2hgOI}2(_gbqhJq$EiszZq{b%9u1X}K zdMh5Cqv#K?qShSz5qi*zNIIT~_-L3Mm4&;rCdh zt?z2zM9(4XABaO1^jsCJcnLf_YV{5ODq|^Z1yHsEpmPn9cuBs(lK4WdDrdQn<8BxF zLFEEt8dlYPl}aH@a17Ui3EwHAGuR8xrO$D?!rd-&cqIH#^{;wlTi)}l+r`HZJ{jrH z{xp6G-eLjFhhFNJ9UAFc*SitMEiJ|j7o&t&*Ua22?~XW1fuIxrmn~Gq9X8@j=tvW~ z4-d4{s{an(1R`CwF@?L584ny~J153H#v^IW3Liu7;M*&4%p^M7JYV50rYvu9vg-E6sZ~+sBoZ zYhY4Pyjy8(LXp#T?)FCk^N}LHkGy6JxNa2AFK`u(6S#~XK4P@ah|#YG7YUX)48)tQ zR@3sp67Ct(Z2qa4$dLX}roFD)4y;+MWQn7#m&awWhy^pnvUtO^SRS948McUT&7A5U i&Am(!f0;RU4U^HI;TdKOmzmC{4$}q;{hq~eZ2B);Zl3f2 diff --git a/__pycache__/salt2.cpython-312.pyc b/__pycache__/salt2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ecbc1a086a8b687e947c7c06759d1a817b36df9 GIT binary patch literal 3663 zcmbVPO>7&-6`th|mn;5=luZ4`u~xPsYi<3cf}0d>(!hzN=BH+zO7(%Q!LB$XaV2ue z&hDxqK>=)Gv;x|Lf*fq1Eufszz&f-*fwn+^y2zo2UMNd}n1uqQO^YHoq4LS6zS-rH zvMLur7U0b6`@BY5r`y}AOc&-jb~U~eHD{S+2=5J71~1QN&#g+MwW z@U@s1(<00`j|;(c(4__QQX!NM738!G?@=P%#_0|cBO#c>Fv~DUNGIulIZEOrLBh9# z=@{)Kk<~GTSS)~O9DcETO*`%h-r^@hSya0!&%aVE6f}b<4kQD#nl&UaA-C!!ff(b6ovXqYK@m*?pZZE9qx8gOD{M*^~fzTk@Fui7&ax3Pbwb)hP( zbhLT?iUf(Y9l96r-q!=wz)HmDwPzxej_q(uO00@2u{PIVkqC*lr3CZ@sTk=Dpepjc z&Lgwkb+366&^q0MZgQr*_d2fPCs>edOTk{JOZAZ6FCf)N`oDnGmpjb>8GLMSVIHYZ zr5F#x`+^hC>!y{_^2yy?*ZFX#^x=^MU9cGiYKG0B3Pa) z>RDEybgDdiQ_Jf_r_3|DWrAkpcwVm+4fhBNPiOwYNxKKsTVmYO z8|=dmmcUd8^IWg=zO0$_xhoT^MR(h#4}hBk_5a~AZ`Vg|JliKV?Q!@_n(E+8EuW_f zmU2Wf?E*Ed8~l|yof;t-(_VDB z%mh_<_gR*G6Srx&J{ zb!&?I${nUBOJygPS+w=MZvwjjno;!0>2`@NTUdM`KR?)tzBha&15KSF^%srGHq2qyQj zvV)ba!TR)@b*${+?=bZ1;#2%@3{ZK~1?tOf22cNrJsXzP8IzG3DUv1#7CT*=gKI776|1a(I z?+^Lk561WLzz!a0$OCmH1?LJ5K12a9O-9k~+Vvyw7s@vK6JI0VGu^Im8y0tv)#6pr zO0$}CG5-N-v&;GvK5q+tP5(aVtPAS_i+3CMA%Uv!OZ4l2-$9_;w0UAR@HnrG)nE!r zS4Rt`6^WOJt4c-UsdZIxi<^oW2M$Z{;UWx9u*9IXLM~%KrO9{#I$@qonKdmBMF!5u zamLPbMpvd;sxm%0*=3+V2S4-cFx1dN-^kX&d$-=b^+)n&<4?w3-}N1wtj%m(I=~$p zbDLK-M>nSS@Syj0b#uHP9@@i7BNAmJp#PYMc}I0UyakId>tXQwDV-UpGHiO{foL>i z#G+QK3zK6JD2vDFt==x6a!{30Y!vphlQ8__SMT^;!cta?jL}ugb-#Va<=$y?m$bZ@ z&3(?%qe_gM>cxh~f%Wo%M~T6G39d9ug~{Zy&3MFH@$iZRn*`pATk<8#z&#=9>}qJ=g6M?Oe3CvfSd>gY|wf*X53JuVezCZ z5E0u@J$}KMLAL;I>q7goMDR&y|DqeCvH%cv-Nbls?0y(-wkxyJ>PFPKTEU7kp{OF` zLT(ZDTomvMD&|>W@-+03+u!okiQdk>0#dW^Gx@?Rhujw$gss-))3;+DwZVLRA5~1TPWUYAg8@K!0VXHxwkY1YOvY2g&3o5( zF6Nr`V~w5TJQt??gssAX3z}}QuL3CrKl4o(0G+YHpJ!@wjY!vi?;Cp%8as$z+>NVsSv`mh-o3ILQJ~wn6~vJ< z8UC$I1};+)qn6E#N*|c*+CO>iu}%%ukegcRZt+uEftn@wm%*L~h9mOQ7;x8&XOt?W zK<5hAN&5dLzGqtOmQqzxMYyPOEl)DxgfbaY%w{r9IFnhj!4R6sFzyvL0D~i0!$4_T~iG0)fvT4!6>|46^K5~!lmgq argparse.ArgumentParser: default="pbkdf2", help="Hash-Algorithmus (Standard: pbkdf2)", ) + + subparsers.add_parser( + "list-algorithms", help="Zeigt alle verfügbaren Hash-Algorithmen an." + ) + return parser @@ -86,7 +91,7 @@ def _normalize_args(argv: Sequence[str] | None) -> list[str]: if not argv: return [] # If it starts with a subcommand, leave as-is - if argv[0] in {"hash", "verify"}: + if argv[0] in {"hash", "verify", "list-algorithms"}: return list(argv) # If it starts with help flags, leave as-is if argv[0] in {"-h", "--help"}: @@ -104,6 +109,14 @@ def main(argv: Sequence[str] | None = None) -> int: arg_list = list(argv) if argv is not None else sys.argv[1:] args = parser.parse_args(_normalize_args(arg_list)) + if args.command == "list-algorithms": + from algorithms import list_algorithms + + print("Verfügbare Algorithmen:") + for algo in list_algorithms(): + print(f" - {algo}") + return 0 + if args.command == "verify": if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm): print("✓ Passwort korrekt") diff --git a/salt2.py b/salt2.py index 2d19fe7..a49e628 100644 --- a/salt2.py +++ b/salt2.py @@ -10,6 +10,8 @@ from salt import hash_password, verify_password def _build_parser() -> argparse.ArgumentParser: + from algorithms import list_algorithms + parser = argparse.ArgumentParser( description="CLI für Password-Hashing (PBKDF2, Argon2, bcrypt) inklusive Verify-Modus." ) @@ -19,6 +21,13 @@ def _build_parser() -> argparse.ArgumentParser: "generate", help="Erzeugt ein neues Salt+Hash Paar." ) generate_parser.add_argument("password", help="Klartext-Passwort zum Hashen.") + generate_parser.add_argument( + "--algorithm", + "-a", + choices=list_algorithms(), + default="pbkdf2", + help="Hash-Algorithmus (Standard: pbkdf2)", + ) verify_parser = subparsers.add_parser( "verify", @@ -27,29 +36,52 @@ def _build_parser() -> argparse.ArgumentParser: verify_parser.add_argument("password", help="Passwort zum Prüfen.") verify_parser.add_argument("salt", help="Base64-kodiertes Salt.") verify_parser.add_argument("hash", help="Base64-kodierter Hash.") + verify_parser.add_argument( + "--algorithm", + "-a", + choices=list_algorithms(), + default="pbkdf2", + help="Hash-Algorithmus (Standard: pbkdf2)", + ) + + subparsers.add_parser( + "list-algorithms", help="Zeigt alle verfügbaren Hash-Algorithmen an." + ) + return parser def _command_generate(args: argparse.Namespace) -> int: - salt, hash_value = hash_password(args.password) + salt, hash_value = hash_password(args.password, algorithm=args.algorithm) print(f"Salt: {salt}") print(f"Hash: {hash_value}") return 0 def _command_verify(args: argparse.Namespace) -> int: - if verify_password(args.password, args.salt, args.hash): + if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm): print("✓ Passwort korrekt") return 0 print("✗ Passwort falsch") return 1 +def _command_list_algorithms() -> int: + from algorithms import list_algorithms + + print("Verfügbare Algorithmen:") + for algo in list_algorithms(): + print(f" - {algo}") + return 0 + + def main(argv: Sequence[str] | None = None) -> int: parser = _build_parser() args = parser.parse_args(argv) if args.command == "generate": return _command_generate(args) + if args.command == "list-algorithms": + return _command_list_algorithms() return _command_verify(args) diff --git a/tests/__pycache__/test_cli2.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_cli2.cpython-312-pytest-9.0.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc8109b1f183681c47b6acf7547c092efb1efdbf GIT binary patch literal 3295 zcmeHJO>Epm6!v(%-oI?RAy68Airf4su4vMRmQsndjUZJ~tE%CEP=sW;JKl{`dv`VC z{%r6{0TrnS?r`9MIG{w~&?Cp3kPw&6Z;>Sg0xsN8bISqo-gs>*i*2AS?H!**r}H7N`o?(a*I&8CHuseZ;eV)E9Iy7w;uY6Hm^id(o?Xh5UQY0 zMPBJx(6ond6>UBnNlCxb5${NRpew#u#>5IGFIJ-*S+_?|JRJ*rJ!%B{jp z&77WJIPs3f>`GJPrsGX$Z(MszGs|VG;c@NcNzOOU_RXnGd+43h6 zgINt$uDcDBSwRm>8#iy;j%U|+pgMK1;K31t&(&``RG1o7r+(9PjGF0MLC)Z2#li?d z@0570U9U|uR%b!KQK>W6^bG4>!!c{72Q$lRP{22Y8J_M|Ug=g|?N-i)N*L|6FvU@GoMzmYTUD!n< z-cAFzlMO@kG(7wxfJN!&(#5ti{qdQ0>EcG=5{Ro)0PVu1)v2|@^dkj-Tb+SWi*XJ8 zLTw`vH^I1cF&thOjw<>67HeEIA5h!IyCtn^eqWkNTF#e zQ{qcLsj4kGsS!V}tX7JuEyYjyir}iWp9ZeNbr5YH*Zhe_GkzMlDvLf{T$S^3k>UJ4 z4D=VSO5*RsB3?xHF`oigB_6%eTatdS2XIyHpM8=QIKVCyF~$LQN@Pd#9j;Pof%f=n zhpRGPUzBlIq^F{>r1vvXq^Jk3DgsBz$W!p7TS~L2_b!~?!&P@e^7>oKVz@;NPZBud za&Z9!YUs%Dv=MB@a0eM~j-V1&L16TU??f%Fx`F2_+>D~yjBDr`Y8#2T3D%{n;qbcfOynD#h;axIwv`z+0;%p_=bNI=DXIQ{(||Qf z+JE4tadJ=fLS1N%&AsHJ!8JInU9(2V77jfz^KZ(OJq;_yUIgemJHui+mJ|~PP7XtE zbY98Ezz?rd`~kqC^i$vQc4@G!=vXg<8-*bdmk2<+FtkM03i=}je_Ne_P>XR5{X%Uc z5jVlOG#C!A3&&VqBC&5QY%98`6}=c_ql=3monXKSuR3GlZ3C_!W6Zh>mo>Py#6v-* zGsXmEoq*30G!~@AZ7*cAVH`yg5#PToi5H4B+s(f2*6D4>y1>rDJl^#=lGg7jLdcJj ebYI$%j{YDWeyqymGI^X&k+;cCk4#R85Pt&KL8hPp literal 0 HcmV?d00001 diff --git a/tests/__pycache__/test_hashing.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_hashing.cpython-312-pytest-9.0.1.pyc index 3c68c5b0a56b4d2f89170d204996d53bcad3b862..7c6d81577a51e821a827d9b752bb442911a77d51 100644 GIT binary patch delta 1350 zcmZ8gO-vg{6yEW!*K04fgKcbRTv&(_w4@vY%C9MuD7Z+iaFi-l#lGl3g{R$QDNz(zUPzgpAMlXzQRK6!n2dNJ* zR3^HAdu`n7I~dJbR-W;3`7$SRlx*@2xtAR9i}Ews2c?<9-^o9bp4yMTzr9Ihbctb8 zrXsWfG{jQ*Jm@xpr}%m#+Q;IUNg#9r=rVH)A#nn#`pG~o9U34+tKAOY z^pHOOTl@TpR{0>xL;PxFki>W?lDHH>B8t#~@IfbqbphK@KP#29&bI7f_7Uom{MX1N zbX20}$OV2S`h;lwcDSpi#{Tn!Hc7*uGv|uTunP+ZJd*h4^QPfV`Gd_e*Af;T1ejv& zfbVS)EgW^-P9&l0ABl5h?EB8=Zc~rx+upG}e%~EZ^;AWw@DWiL{Z!<+*tfN};s!}M zDM*V|F9i_|R7G2avd&ZSRD=qth`*>mODnv?MEb;@nN%%y(hd+w@12niH zugHQ>_3`Dn&L6hN#*fu6`}EMt#MgOd+$wANJBDQ&i*wrbt6yA84{3#*RmfW!aHgp( z7umeF3};kaviV=}j@6a0nV-wemIq64){vzPx(EzS9sA;bm`<@Uk|z)@BV0ikLHHEG zIYZ^xqShIn#C#IrV}wxvO99ZdRn2)=H_RGCcHXkHGr8IM67GhCL@(9A3@J1G5z4hLqz3A}WFW0W!Kl4f!l7n?A^K*3HUIR8Yv`|iE!!Ds;C0>T&qe#cG0sp?#Y17&2PNSDm~D4T>7fuD7j V7rOe|Un?S+Apf=n$aUiA{|C3vX5;_> delta 619 zcmZvY&ubG=5XUp!f+`Hvl}G6 z6-sYCv=6T$-aJYU-n_yG(vveAF$g}`&kVEg&G$R=we#AM7pA$8q0>)R zf>Z5GR^ahj^HxLmwBzjFf~R{~ig}7Tii@5&&g_|Eu#FEVcXM#4zT_T(t*W9y`HuJ` z1hqn4f&K<+lehZ!T#YMs77hL}&tSPk8;-Z>-aB)1yTHXIMv+lsSd3-H3c(gQ?aPIA z#;UqBZBeB(X{aN8S?x#(&B=Ya1yG;7DmmVQ>Z4q`+691W;<^L#BHZ}5WfIT;EMcj8iJ(zCg zI&H;LKgw&+QlfHEuXD4xM4%s!u)k}|#MmDNVI1@$EU5EM=Rvys4VFkfYMf;mHAa)+ oFj|E78RUkk)TME|H}FQGe+S!C1Nx$Cs(f`L|I^Um9;7#a1G98}y8r+H diff --git a/tests/__pycache__/test_integration.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_integration.cpython-312-pytest-9.0.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c64752c82ac405c24d517a3ce685512595686624 GIT binary patch literal 7650 zcmeGhO>Em_)Q%lHP11C2w{BzI@U%`W5^dLZ{b5>x@l!E=1`@r{!z?$SnOa?|bk2p5OQR zdtcw5zi)4kGjKgLdU9Vx8Ri!x{43HxHr@qfnIX&!Ls$~g*qMmiayp-hu#6s^5m;|T zoDtoT*i4LYH<+0=!UK$xD8K{}047V3v{)X$WY}t!Dt68^BwMv?OPVvOq!%?i*RN<< zljiJsT`DM+bJoy7Y7Pc!5bb+xBHOcP^Iee_btTc9jHgg{I$HO+{G+3BF zT=h=8tS@yJzKG99l3#LV*yAcs+DWRy=UA7D+K2rWG)X$*_l0dG>Pzz5&sfs2$W~dn zxoU(Qs0yHy7s4le4D=yXA`47atcbg6qq8D{HoEYMP#aAv+i5QZe?|LVYoRu}pOZEO z(1r-wIM~w0uKz=Orj1fZ`cV1BX)vqOM{m6M*3_`18l>Mg`=OB*a*Fik`>^cVvuGEJw!_=%qD|4r*4WL+ z z!o86d>U7E(Mbl)e7HG!Q3kp@8Bn->5#T>XZ171-xfx~i+$kx1B)QHO}XEk$H(PTr> zRi{n1lsOe8IH`;7YlCBan^KcH-7oOg|d!bs4qT6s1IGwj$L(Rmw zW@5x6JZWRi#6&aO*=AzcLZ>G+uFbQ9^QNv2=1nCtpVJ4kIeQQf(V*+WT`1>5$Bt*~ zn7c{dX_7Nqu6dk}f@~3PYY|-5b>`Rh!?&;8y7E(7^7e^aCq6%Vcj~JTzMc5)lX~pK z^~1;3+dKad`IK;d>QS6YcC96P?m4fO?|$E_*L=a*fB|gGPr(1@In!^5&RF zt%m4*m)Yz70mTLU0Rr6XiLN?-YH4m+zoP?kb9_m!Cr+*L_uJ zKAd9uKq2p8D5f)xpt!6QjrKHc!+!M76!Q?8LV)gqVuC?2vZaRrT&BH<_8`DOoE}AR z9Kp)~p6~$l9P~7dx;NK0qy7qTpl97kw2XRS)WOcuG1!!rorO(+E8ogoTbZkO6qx)@ zIrd)WO1_<)&CpW8-YHkwW@kOty|J_2T-)sIBy8UaxaoNW+l&jw)mXgRSOU-R?}NB3 z2Zb^2YyNK(#G+4G5EI;jn7E}N7Wr?EV#6