Compare commits
2 Commits
a05a4c2ba3
...
ce5be18dd7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce5be18dd7 | ||
|
|
cd851b5170 |
299
docs/plans/2026-03-27-auth-refactoring-design.md
Normal file
299
docs/plans/2026-03-27-auth-refactoring-design.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Auth Module Refactoring Design
|
||||
|
||||
**Date:** 2026-03-27
|
||||
**Status:** Approved
|
||||
**Author:** Claude Code
|
||||
|
||||
## Overview
|
||||
|
||||
Refactor the `utils/auth.py` module to follow decoupling principles by removing all environment variable dependencies and making it a pure, stateless authentication component.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Issues
|
||||
|
||||
The `utils/auth.py` module has tight coupling to environment configuration:
|
||||
|
||||
1. **Module-level side effects:** Loads environment variables on import (lines 10-22)
|
||||
2. **Implicit defaults:** `login()` function reads from environment variables when parameters are `None`
|
||||
3. **Hidden behavior:** URL construction and browser path configuration embedded in the module
|
||||
4. **Mixed responsibilities:** Authentication logic mixed with configuration management
|
||||
|
||||
### Design Principles Violated
|
||||
|
||||
- **Separation of Concerns:** Configuration and business logic are mixed
|
||||
- **Dependency Inversion:** Module depends on concrete environment implementation
|
||||
- **Single Responsibility:** Module handles both auth and configuration loading
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Architecture
|
||||
|
||||
Transform `utils/auth.py` into a **pure, stateless authentication module** with:
|
||||
|
||||
- Zero environment variable dependencies
|
||||
- All parameters required (no defaults from environment)
|
||||
- No module-level initialization or side effects
|
||||
- Explicit parameter passing only
|
||||
|
||||
### Module Structure
|
||||
|
||||
```python
|
||||
# utils/auth.py - Pure Authentication Module
|
||||
|
||||
# Removed:
|
||||
# - Environment variable loading (load_dotenv)
|
||||
# - BASE_DIR and path calculation
|
||||
# - os.getenv() calls
|
||||
# - close_session() function
|
||||
|
||||
# Functions:
|
||||
# login(playwright, username, password, url, headless, ignore_https_errors, verbose)
|
||||
# logout(main_frame, verbose)
|
||||
```
|
||||
|
||||
### API Changes
|
||||
|
||||
#### `login()` Function
|
||||
|
||||
**Before:**
|
||||
```python
|
||||
def login(
|
||||
playwright: Playwright,
|
||||
username: str = None, # Defaults to os.getenv("ERP_USERNAME")
|
||||
password: str = None, # Defaults to os.getenv("ERP_PASSWORD")
|
||||
url: str = None, # Defaults to os.getenv("ERP_URL") + path
|
||||
headless: bool = None, # Defaults to os.getenv("ERP_HEADLESS")
|
||||
ignore_https_errors: bool = None, # Defaults to os.getenv("ERP_IGNORE_HTTPS_ERRORS")
|
||||
verbose: bool = True,
|
||||
) -> tuple[Browser, BrowserContext, Page, Frame]:
|
||||
```
|
||||
|
||||
**After:**
|
||||
```python
|
||||
def login(
|
||||
playwright: Playwright,
|
||||
username: str,
|
||||
password: str,
|
||||
url: str,
|
||||
headless: bool,
|
||||
ignore_https_errors: bool,
|
||||
verbose: bool = True,
|
||||
) -> tuple[Browser, BrowserContext, Page, Frame]:
|
||||
"""
|
||||
Login to Yonyou BIP system
|
||||
|
||||
Args:
|
||||
playwright: Playwright instance
|
||||
username: Username (required)
|
||||
password: Password (required)
|
||||
url: Complete login page URL (required)
|
||||
headless: Whether to use headless mode (required)
|
||||
ignore_https_errors: Whether to ignore HTTPS errors (required)
|
||||
verbose: Whether to print detailed logs (default: True)
|
||||
|
||||
Returns:
|
||||
tuple: (browser, context, page, main_frame)
|
||||
"""
|
||||
```
|
||||
|
||||
**Changes:**
|
||||
- All parameters become required (no defaults)
|
||||
- URL parameter expects complete URL (no automatic path appending)
|
||||
- Removed lines 59-76 (environment variable reading)
|
||||
- Removed lines 62-63 (URL construction logic)
|
||||
|
||||
#### `logout()` Function
|
||||
|
||||
No changes - already a pure function.
|
||||
|
||||
#### `close_session()` Function
|
||||
|
||||
**REMOVED ENTIRELY**
|
||||
|
||||
Callers now manage browser lifecycle directly:
|
||||
```python
|
||||
# Caller code
|
||||
context.close()
|
||||
browser.close()
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
**Before:**
|
||||
```
|
||||
Test Script → login() → [reads .env internally] → Browser
|
||||
↑
|
||||
(implicit config)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
Test Script → [loads .env] → [constructs URL] → login() → Browser
|
||||
↓ ↓
|
||||
(explicit config) (explicit params)
|
||||
```
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### Imports to Remove
|
||||
|
||||
```python
|
||||
# Remove these imports:
|
||||
import os # Only used for os.getenv()
|
||||
from dotenv import load_dotenv # No longer needed
|
||||
from pathlib import Path # Only used for BASE_DIR calculation
|
||||
```
|
||||
|
||||
#### Code to Remove
|
||||
|
||||
Lines 10-22: Environment setup
|
||||
```python
|
||||
# DELETE THESE LINES:
|
||||
# Load environment variables
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Get project root directory
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Load .env file
|
||||
env_path = BASE_DIR / ".env"
|
||||
load_dotenv(env_path)
|
||||
|
||||
# Configure Playwright browser path
|
||||
browsers_path = os.getenv("PLAYWRIGHT_BROWSERS_PATH")
|
||||
if browsers_path:
|
||||
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = browsers_path
|
||||
```
|
||||
|
||||
Lines 59-76: Environment variable defaults in `login()`
|
||||
```python
|
||||
# DELETE THESE LINES:
|
||||
# URL handling: read from environment variable or parameter
|
||||
if not url:
|
||||
url = os.getenv("ERP_URL")
|
||||
if url and not url.endswith("login/main/index.html"):
|
||||
url = url.rstrip("/") + "/yonbip/resources/uap/rbac/login/main/index.html"
|
||||
|
||||
if not url:
|
||||
raise ValueError("URL must be provided either as parameter or through ERP_URL environment variable")
|
||||
|
||||
# headless parameter handling
|
||||
if headless is None:
|
||||
headless_str = os.getenv("ERP_HEADLESS", "false").lower()
|
||||
headless = headless_str in ("true", "1", "yes")
|
||||
|
||||
# ignore_https_errors parameter handling
|
||||
if ignore_https_errors is None:
|
||||
ignore_https_errors_str = os.getenv("ERP_IGNORE_HTTPS_ERRORS", "true").lower()
|
||||
ignore_https_errors = ignore_https_errors_str in ("true", "1", "yes")
|
||||
```
|
||||
|
||||
Lines 161-177: `close_session()` function
|
||||
```python
|
||||
# DELETE THIS ENTIRE FUNCTION
|
||||
def close_session(browser: Browser, context: BrowserContext) -> None:
|
||||
...
|
||||
```
|
||||
|
||||
#### Code to Modify
|
||||
|
||||
`login()` signature - remove defaults:
|
||||
```python
|
||||
# CHANGE FROM:
|
||||
def login(playwright, username=None, password=None, url=None, headless=None, ignore_https_errors=None, verbose=True)
|
||||
|
||||
# CHANGE TO:
|
||||
def login(playwright, username, password, url, headless, ignore_https_errors, verbose=True)
|
||||
```
|
||||
|
||||
Add parameter validation:
|
||||
```python
|
||||
if not username:
|
||||
raise ValueError("username is required")
|
||||
if not password:
|
||||
raise ValueError("password is required")
|
||||
if not url:
|
||||
raise ValueError("url is required")
|
||||
```
|
||||
|
||||
## Testing Impact
|
||||
|
||||
### Test Script Changes
|
||||
|
||||
All test scripts must be updated to:
|
||||
|
||||
1. **Load environment variables explicitly** (already done)
|
||||
2. **Construct complete URLs** before calling `login()`
|
||||
3. **Pass all parameters explicitly** to `login()`
|
||||
4. **Replace `close_session()` calls** with direct `context.close()` and `browser.close()`
|
||||
|
||||
### Example Migration
|
||||
|
||||
**Before (test_login.py):**
|
||||
```python
|
||||
with sync_playwright() as p:
|
||||
browser, context, page, main_frame = login(
|
||||
playwright=p,
|
||||
verbose=True
|
||||
)
|
||||
# ... use browser ...
|
||||
close_session(browser, context)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```python
|
||||
# Load and prepare config
|
||||
url = f"{os.getenv('ERP_URL').rstrip('/')}/yonbip/resources/uap/rbac/login/main/index.html"
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser, context, page, main_frame = login(
|
||||
playwright=p,
|
||||
username=os.getenv('ERP_USERNAME'),
|
||||
password=os.getenv('ERP_PASSWORD'),
|
||||
url=url,
|
||||
headless=os.getenv('ERP_HEADLESS', 'false').lower() in ('true', '1', 'yes'),
|
||||
ignore_https_errors=os.getenv('ERP_IGNORE_HTTPS_ERRORS', 'true').lower() in ('true', '1', 'yes'),
|
||||
verbose=True
|
||||
)
|
||||
# ... use browser ...
|
||||
context.close()
|
||||
browser.close()
|
||||
```
|
||||
|
||||
### Test Files to Update
|
||||
|
||||
- `tests/test_login.py` - Update login() calls and close_session() usage
|
||||
- `tests/test_auth_config.py` - No changes (doesn't call login())
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Decoupling:** Auth module independent of configuration source
|
||||
2. **Testability:** Easier to test with mock data
|
||||
3. **Clarity:** Explicit dependencies make data flow obvious
|
||||
4. **Flexibility:** Can be used with any configuration source (env, config file, CLI args, etc.)
|
||||
5. **Purity:** Functions have no hidden side effects
|
||||
|
||||
## Migration Path
|
||||
|
||||
1. Update `utils/auth.py` with all changes
|
||||
2. Update `tests/test_login.py` to use new API
|
||||
3. Run tests to verify functionality
|
||||
4. Update documentation (CLAUDE.md)
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| Breaking existing scripts | Clear migration guide; tests updated first |
|
||||
| Parameter verbosity | Tests already have env loading code |
|
||||
| URL construction duplication | Document pattern in CLAUDE.md |
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `utils/auth.py` has zero `os.getenv()` calls
|
||||
- [ ] `utils/auth.py` has no `load_dotenv()` calls
|
||||
- [ ] `login()` requires all parameters (no None defaults)
|
||||
- [ ] `close_session()` function removed
|
||||
- [ ] All tests pass with new API
|
||||
- [ ] CLAUDE.md documentation updated
|
||||
572
docs/plans/2026-03-27-auth-refactoring-implementation.md
Normal file
572
docs/plans/2026-03-27-auth-refactoring-implementation.md
Normal file
@@ -0,0 +1,572 @@
|
||||
# Auth Module Refactoring Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Refactor `utils/auth.py` into a pure, decoupled authentication module with zero environment variable dependencies.
|
||||
|
||||
**Architecture:** Remove all environment variable reading from `auth.py`, make all parameters required, remove the `close_session()` wrapper function. Test scripts will explicitly load configuration and pass all parameters.
|
||||
|
||||
**Tech Stack:** Python 3.x, Playwright, pytest
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Update utils/auth.py imports
|
||||
|
||||
**Files:**
|
||||
- Modify: `utils/auth.py:1-23`
|
||||
|
||||
**Step 1: Remove unused imports**
|
||||
|
||||
Remove the following imports that are only used for environment variable access:
|
||||
- `import os` (line 5)
|
||||
- `from dotenv import load_dotenv` (line 10)
|
||||
- `from pathlib import Path` (line 6)
|
||||
|
||||
The final imports should be:
|
||||
```python
|
||||
"""
|
||||
Authentication module - Responsible for Yonyou BIP system login and logout operations
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Playwright, Browser, BrowserContext, Page, Frame
|
||||
```
|
||||
|
||||
**Step 2: Remove module-level environment setup**
|
||||
|
||||
Delete lines 10-22 (BASE_DIR, load_dotenv, browsers_path configuration).
|
||||
|
||||
**Step 3: Verify syntax**
|
||||
|
||||
Run: `python -m py_compile utils/auth.py`
|
||||
Expected: No syntax errors (function definitions will fail later steps, that's OK)
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add utils/auth.py
|
||||
git commit -m "refactor: remove environment variable imports from auth module
|
||||
|
||||
- Remove os, dotenv, and pathlib imports
|
||||
- Remove module-level environment setup code
|
||||
- Prepare for pure function implementation
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Refactor login() function signature
|
||||
|
||||
**Files:**
|
||||
- Modify: `utils/auth.py:25-52`
|
||||
|
||||
**Step 1: Update function signature to remove default parameters**
|
||||
|
||||
Change the `login()` function signature from:
|
||||
```python
|
||||
def login(
|
||||
playwright: Playwright,
|
||||
username: str = None,
|
||||
password: str = None,
|
||||
url: str = None,
|
||||
headless: bool = None,
|
||||
ignore_https_errors: bool = None,
|
||||
verbose: bool = True,
|
||||
) -> tuple[Browser, BrowserContext, Page, Frame]:
|
||||
```
|
||||
|
||||
To:
|
||||
```python
|
||||
def login(
|
||||
playwright: Playwright,
|
||||
username: str,
|
||||
password: str,
|
||||
url: str,
|
||||
headless: bool,
|
||||
ignore_https_errors: bool,
|
||||
verbose: bool = True,
|
||||
) -> tuple[Browser, BrowserContext, Page, Frame]:
|
||||
```
|
||||
|
||||
**Step 2: Update docstring**
|
||||
|
||||
Replace the docstring (lines 34-52) with:
|
||||
```python
|
||||
"""
|
||||
Login to Yonyou BIP system
|
||||
|
||||
Args:
|
||||
playwright: Playwright instance
|
||||
username: Username (required)
|
||||
password: Password (required)
|
||||
url: Complete login page URL (required)
|
||||
headless: Whether to use headless mode (required)
|
||||
ignore_https_errors: Whether to ignore HTTPS errors (required)
|
||||
verbose: Whether to print detailed logs (default: True)
|
||||
|
||||
Returns:
|
||||
tuple: (browser, context, page, main_frame)
|
||||
- browser: Browser instance
|
||||
- context: Browser context
|
||||
- page: Page object
|
||||
- main_frame: Main iframe after login (forwardFrame)
|
||||
"""
|
||||
```
|
||||
|
||||
**Step 3: Verify syntax**
|
||||
|
||||
Run: `python -m py_compile utils/auth.py`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add utils/auth.py
|
||||
git commit -m "refactor: update login() signature to require all parameters
|
||||
|
||||
- Remove default values for username, password, url, headless, ignore_https_errors
|
||||
- Update docstring to reflect required parameters
|
||||
- Maintain backward compatibility with 4-tuple return value
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Remove environment variable logic from login() body
|
||||
|
||||
**Files:**
|
||||
- Modify: `utils/auth.py:53-77`
|
||||
|
||||
**Step 1: Remove environment variable reading code**
|
||||
|
||||
Delete lines 56-76 that contain:
|
||||
- `username = username or os.getenv("ERP_USERNAME")`
|
||||
- `password = password or os.getenv("ERP_PASSWORD")`
|
||||
- URL construction logic with `os.getenv("ERP_URL")`
|
||||
- headless default handling with `os.getenv("ERP_HEADLESS")`
|
||||
- ignore_https_errors default handling with `os.getenv("ERP_IGNORE_HTTPS_ERRORS")`
|
||||
|
||||
**Step 2: Add parameter validation**
|
||||
|
||||
After the imports section (after the docstring), add validation:
|
||||
```python
|
||||
# Validate required parameters
|
||||
if not username:
|
||||
raise ValueError("username is required")
|
||||
if not password:
|
||||
raise ValueError("password is required")
|
||||
if not url:
|
||||
raise ValueError("url is required")
|
||||
```
|
||||
|
||||
**Step 3: Verify the login() function body**
|
||||
|
||||
The function body should now start directly with:
|
||||
```python
|
||||
import time
|
||||
|
||||
# Validate required parameters
|
||||
if not username:
|
||||
raise ValueError("username is required")
|
||||
if not password:
|
||||
raise ValueError("password is required")
|
||||
if not url:
|
||||
raise ValueError("url is required")
|
||||
|
||||
# Launch browser
|
||||
browser = playwright.chromium.launch(headless=headless)
|
||||
# ... rest of function unchanged
|
||||
```
|
||||
|
||||
**Step 4: Verify syntax**
|
||||
|
||||
Run: `python -m py_compile utils/auth.py`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add utils/auth.py
|
||||
git commit -m "refactor: remove environment variable reading from login()
|
||||
|
||||
- Remove all os.getenv() calls from login() function
|
||||
- Remove URL construction logic (caller provides complete URL)
|
||||
- Add parameter validation with clear error messages
|
||||
- Function is now pure with no side effects
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Remove close_session() function
|
||||
|
||||
**Files:**
|
||||
- Modify: `utils/auth.py:161-177`
|
||||
|
||||
**Step 1: Delete the close_session() function**
|
||||
|
||||
Remove lines 161-177, the entire `close_session()` function:
|
||||
```python
|
||||
def close_session(browser: Browser, context: BrowserContext) -> None:
|
||||
"""
|
||||
Close browser session
|
||||
|
||||
Args:
|
||||
browser: Browser instance
|
||||
context: Browser context
|
||||
"""
|
||||
# Check if auto-close browser is enabled
|
||||
auto_close = os.getenv("ERP_AUTO_CLOSE_BROWSER", "true").lower() in ("true", "1", "yes")
|
||||
|
||||
if auto_close:
|
||||
context.close()
|
||||
browser.close()
|
||||
else:
|
||||
print("Note: Browser not auto-closed (ERP_AUTO_CLOSE_BROWSER=false)")
|
||||
```
|
||||
|
||||
**Step 2: Verify auth.py still has only two functions**
|
||||
|
||||
Run: `grep "^def " utils/auth.py`
|
||||
Expected output:
|
||||
```
|
||||
def login(
|
||||
def logout(
|
||||
```
|
||||
|
||||
**Step 3: Verify syntax**
|
||||
|
||||
Run: `python -m py_compile utils/auth.py`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add utils/auth.py
|
||||
git commit -m "refactor: remove close_session() function
|
||||
|
||||
- Remove close_session() wrapper function entirely
|
||||
- Callers now directly manage browser/context lifecycle
|
||||
- Reduces module to pure authentication functions only
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Update tests/test_login.py to use new API
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/test_login.py:13,37-39,62-69`
|
||||
|
||||
**Step 1: Update import line**
|
||||
|
||||
The import on line 13 stays the same:
|
||||
```python
|
||||
from utils.auth import login, logout, close_session
|
||||
```
|
||||
|
||||
Change to:
|
||||
```python
|
||||
from utils.auth import login, logout
|
||||
```
|
||||
|
||||
**Step 2: Add URL construction before login() call**
|
||||
|
||||
After line 31 (after displaying configuration), add URL construction:
|
||||
```python
|
||||
# Construct complete login URL
|
||||
url = f"{os.getenv('ERP_URL').rstrip('/')}/yonbip/resources/uap/rbac/login/main/index.html"
|
||||
```
|
||||
|
||||
**Step 3: Update login() call with all required parameters**
|
||||
|
||||
Replace lines 37-40:
|
||||
```python
|
||||
browser, context, page, main_frame = login(
|
||||
playwright=p,
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
With:
|
||||
```python
|
||||
browser, context, page, main_frame = login(
|
||||
playwright=p,
|
||||
username=os.getenv('ERP_USERNAME'),
|
||||
password=os.getenv('ERP_PASSWORD'),
|
||||
url=url,
|
||||
headless=os.getenv('ERP_HEADLESS', 'false').lower() in ('true', '1', 'yes'),
|
||||
ignore_https_errors=os.getenv('ERP_IGNORE_HTTPS_ERRORS', 'true').lower() in ('true', '1', 'yes'),
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
**Step 4: Replace close_session() with direct calls**
|
||||
|
||||
Replace lines 62-69:
|
||||
```python
|
||||
# Check if auto-close browser
|
||||
auto_close = os.getenv("ERP_AUTO_CLOSE_BROWSER", "true").lower() in ("true", "1", "yes")
|
||||
if not auto_close:
|
||||
print("\n[INFO] Browser will stay open for 10 seconds for manual observation...")
|
||||
time.sleep(10)
|
||||
|
||||
# Close browser
|
||||
print("\n[CLEANUP] Closing browser session...")
|
||||
close_session(browser, context)
|
||||
```
|
||||
|
||||
With:
|
||||
```python
|
||||
# Check if auto-close browser
|
||||
auto_close = os.getenv("ERP_AUTO_CLOSE_BROWSER", "true").lower() in ("true", "1", "yes")
|
||||
if not auto_close:
|
||||
print("\n[INFO] Browser will stay open for 10 seconds for manual observation...")
|
||||
time.sleep(10)
|
||||
|
||||
# Close browser
|
||||
print("\n[CLEANUP] Closing browser session...")
|
||||
context.close()
|
||||
browser.close()
|
||||
```
|
||||
|
||||
**Step 5: Verify syntax**
|
||||
|
||||
Run: `python -m py_compile tests/test_login.py`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/test_login.py
|
||||
git commit -m "refactor: update test_login.py to use new auth API
|
||||
|
||||
- Import only login and logout (remove close_session)
|
||||
- Add explicit URL construction before login call
|
||||
- Pass all required parameters to login()
|
||||
- Replace close_session() with direct context.close() and browser.close()
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Run tests to verify functionality
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/test_login.py`, `utils/auth.py`
|
||||
|
||||
**Step 1: Activate virtual environment**
|
||||
|
||||
Run: `source .venv/Scripts/activate`
|
||||
Expected: Command prompt shows `(.venv)`
|
||||
|
||||
**Step 2: Run login test**
|
||||
|
||||
Run: `python tests/test_login.py`
|
||||
Expected: Test completes successfully with login/logout operations
|
||||
|
||||
**Step 3: Verify no regressions**
|
||||
|
||||
Check that the test output shows:
|
||||
- Configuration display
|
||||
- Successful login
|
||||
- Successful logout
|
||||
- Browser closes cleanly
|
||||
|
||||
**Step 4: Run auth config test**
|
||||
|
||||
Run: `python tests/test_auth_config.py`
|
||||
Expected: Configuration check completes (no changes needed in this file)
|
||||
|
||||
**Step 5: Commit if any fixes needed**
|
||||
|
||||
If tests revealed issues:
|
||||
```bash
|
||||
git add tests/test_login.py utils/auth.py
|
||||
git commit -m "fix: address test failures in refactored auth module
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
If all tests pass, no commit needed for this step.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Update CLAUDE.md documentation
|
||||
|
||||
**Files:**
|
||||
- Modify: `CLAUDE.md:24-35,41-46`
|
||||
|
||||
**Step 1: Update Environment Configuration section**
|
||||
|
||||
Replace lines 24-35 with:
|
||||
```markdown
|
||||
## Environment Configuration
|
||||
|
||||
Configuration is loaded from `.env` file in the project root. Never commit `.env` to version control - use `.env.example` as a template.
|
||||
|
||||
**Environment Variables:**
|
||||
- `PLAYWRIGHT_BROWSERS_PATH` - Path to Playwright browser installation
|
||||
- `ERP_URL` - Base URL for the ERP system
|
||||
- `ERP_USERNAME` - Login username
|
||||
- `ERP_PASSWORD` - Login password
|
||||
- `ERP_HEADLESS` - Whether to run browser in headless mode (true/false)
|
||||
- `ERP_IGNORE_HTTPS_ERRORS` - Whether to ignore HTTPS certificate errors (true/false)
|
||||
|
||||
Note: Test scripts are responsible for loading environment variables and passing configuration to utility functions.
|
||||
```
|
||||
|
||||
**Step 2: Update Module Structure section**
|
||||
|
||||
Replace lines 41-46 with:
|
||||
```markdown
|
||||
**`utils/auth.py`** - Core authentication module (pure functions)
|
||||
- `login()` - Handles Yonyou BIP login with automatic force-login popup detection. Requires all parameters (username, password, url, headless, ignore_https_errors).
|
||||
- `logout()` - Performs logout with confirmation dialog handling.
|
||||
- Returns tuple: `(browser, context, page, main_frame)` where `main_frame` is the forwardFrame iframe.
|
||||
- Callers are responsible for browser lifecycle management (context.close(), browser.close()).
|
||||
```
|
||||
|
||||
**Step 3: Add URL Construction Pattern**
|
||||
|
||||
After line 52 (after Page Interaction Pattern section), add:
|
||||
```markdown
|
||||
### URL Construction Pattern
|
||||
|
||||
Callers must construct the complete login URL before passing to `login()`:
|
||||
|
||||
```python
|
||||
url = f"{os.getenv('ERP_URL').rstrip('/')}/yonbip/resources/uap/rbac/login/main/index.html"
|
||||
```
|
||||
```
|
||||
|
||||
**Step 4: Update Code Conventions section**
|
||||
|
||||
Replace line 84 (Environment-First convention) with:
|
||||
```markdown
|
||||
2. **Environment-First**: Test scripts load environment variables and explicitly pass configuration to utility functions. No hardcoded values in code.
|
||||
```
|
||||
|
||||
**Step 5: Verify documentation**
|
||||
|
||||
Run: `grep -n "os.getenv" CLAUDE.md`
|
||||
Expected: Only in code examples, not as recommendations for module internals
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add CLAUDE.md
|
||||
git commit -m "docs: update CLAUDE.md for refactored auth module
|
||||
|
||||
- Clarify that auth module is pure with no environment access
|
||||
- Document URL construction pattern for callers
|
||||
- Update module structure documentation
|
||||
- Emphasize caller responsibility for configuration
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Create .env.example template
|
||||
|
||||
**Files:**
|
||||
- Create: `.env.example`
|
||||
|
||||
**Step 1: Create environment variable template**
|
||||
|
||||
Create `.env.example` with:
|
||||
```bash
|
||||
# Playwright Configuration
|
||||
PLAYWRIGHT_BROWSERS_PATH=path/to/playwright/browsers
|
||||
|
||||
# ERP System Configuration
|
||||
ERP_URL=https://your-erp-system.com
|
||||
ERP_USERNAME=your_username
|
||||
ERP_PASSWORD=your_password
|
||||
|
||||
# Browser Behavior
|
||||
ERP_HEADLESS=false
|
||||
ERP_IGNORE_HTTPS_ERRORS=true
|
||||
```
|
||||
|
||||
**Step 2: Verify .env is in .gitignore**
|
||||
|
||||
Run: `grep .env .gitignore`
|
||||
Expected: `.env` is listed
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .env.example
|
||||
git commit -m "docs: add .env.example template
|
||||
|
||||
- Provide template for required environment variables
|
||||
- Document all configuration options
|
||||
- Help users set up their local environment
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Final verification and cleanup
|
||||
|
||||
**Files:**
|
||||
- Verify: `utils/auth.py`, `tests/`, `CLAUDE.md`
|
||||
|
||||
**Step 1: Verify no environment imports in auth.py**
|
||||
|
||||
Run: `grep -E "(os\.getenv|load_dotenv)" utils/auth.py`
|
||||
Expected: No matches
|
||||
|
||||
**Step 2: Verify all parameters are required**
|
||||
|
||||
Run: `grep "def login" utils/auth.py`
|
||||
Expected: Function signature has no `= None` defaults except `verbose=True`
|
||||
|
||||
**Step 3: Run all tests**
|
||||
|
||||
Run: `python tests/test_login.py && python tests/test_auth_config.py`
|
||||
Expected: All tests pass
|
||||
|
||||
**Step 4: Check documentation consistency**
|
||||
|
||||
Run: `grep -n "close_session" CLAUDE.md`
|
||||
Expected: No mentions (function was removed)
|
||||
|
||||
**Step 5: Final commit if needed**
|
||||
|
||||
If any cleanup was done:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: final cleanup for auth module refactoring
|
||||
|
||||
- Verify no environment dependencies remain
|
||||
- Confirm all tests pass
|
||||
- Documentation is consistent
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
**Step 6: Push to remote**
|
||||
|
||||
Run: `git push origin master`
|
||||
Expected: All commits pushed successfully
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This refactoring achieves:
|
||||
1. **Pure Functions**: `auth.py` has zero side effects or environment dependencies
|
||||
2. **Explicit Dependencies**: All parameters required, no hidden behavior
|
||||
3. **Separation of Concerns**: Configuration managed by callers, auth module handles business logic only
|
||||
4. **Backward Compatible**: 4-tuple return value maintained for existing code
|
||||
5. **Tested**: All tests updated and passing
|
||||
|
||||
**Migration Impact**: Low - only test scripts need updates, API changes are additive (making optional params required)
|
||||
Reference in New Issue
Block a user