@aicgen/aicgen 1.0.0-beta.1
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.
- package/.claude/agents/architecture-reviewer.md +88 -0
- package/.claude/agents/guideline-checker.md +73 -0
- package/.claude/agents/security-auditor.md +108 -0
- package/.claude/guidelines/api-design.md +645 -0
- package/.claude/guidelines/architecture.md +2503 -0
- package/.claude/guidelines/best-practices.md +618 -0
- package/.claude/guidelines/code-style.md +304 -0
- package/.claude/guidelines/design-patterns.md +573 -0
- package/.claude/guidelines/devops.md +226 -0
- package/.claude/guidelines/error-handling.md +413 -0
- package/.claude/guidelines/language.md +782 -0
- package/.claude/guidelines/performance.md +706 -0
- package/.claude/guidelines/security.md +583 -0
- package/.claude/guidelines/testing.md +568 -0
- package/.claude/settings.json +98 -0
- package/.claude/settings.local.json +8 -0
- package/.env.example +23 -0
- package/.eslintrc.json +28 -0
- package/.github/workflows/release.yml +180 -0
- package/.github/workflows/test.yml +81 -0
- package/.gitmodules +3 -0
- package/.vs/ProjectSettings.json +3 -0
- package/.vs/VSWorkspaceState.json +16 -0
- package/.vs/aicgen.slnx/FileContentIndex/5f0ce2a3-fd68-4863-9e23-e428cf1794e3.vsidx +0 -0
- package/.vs/aicgen.slnx/v18/.wsuo +0 -0
- package/.vs/aicgen.slnx/v18/DocumentLayout.json +54 -0
- package/.vs/slnx.sqlite +0 -0
- package/AGENTS.md +121 -0
- package/CLAUDE.md +36 -0
- package/CONTRIBUTING.md +821 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/assets/icon.svg +34 -0
- package/assets/logo.svg +41 -0
- package/bun.lock +848 -0
- package/data/LICENSE +21 -0
- package/data/README.md +203 -0
- package/data/api/basics.md +292 -0
- package/data/api/index.md +8 -0
- package/data/api/pagination.md +142 -0
- package/data/api/rest.md +137 -0
- package/data/api/versioning.md +60 -0
- package/data/architecture/clean-architecture/index.md +7 -0
- package/data/architecture/clean-architecture/layers.md +111 -0
- package/data/architecture/ddd/index.md +8 -0
- package/data/architecture/ddd/strategic.md +89 -0
- package/data/architecture/ddd/tactical.md +132 -0
- package/data/architecture/event-driven/index.md +7 -0
- package/data/architecture/event-driven/messaging.md +242 -0
- package/data/architecture/event-driven/patterns.md +129 -0
- package/data/architecture/feature-toggles/index.md +7 -0
- package/data/architecture/feature-toggles/patterns.md +73 -0
- package/data/architecture/gui/index.md +7 -0
- package/data/architecture/gui/patterns.md +132 -0
- package/data/architecture/hexagonal/ports-adapters.md +132 -0
- package/data/architecture/index.md +12 -0
- package/data/architecture/layered/index.md +7 -0
- package/data/architecture/layered/layers.md +100 -0
- package/data/architecture/microservices/api-gateway.md +56 -0
- package/data/architecture/microservices/boundaries.md +80 -0
- package/data/architecture/microservices/communication.md +97 -0
- package/data/architecture/microservices/data.md +92 -0
- package/data/architecture/microservices/index.md +11 -0
- package/data/architecture/microservices/resilience.md +111 -0
- package/data/architecture/modular-monolith/boundaries.md +133 -0
- package/data/architecture/modular-monolith/structure.md +131 -0
- package/data/architecture/serverless/best-practices.md +322 -0
- package/data/architecture/serverless/index.md +7 -0
- package/data/architecture/serverless/patterns.md +80 -0
- package/data/architecture/solid/index.md +7 -0
- package/data/architecture/solid/principles.md +187 -0
- package/data/database/basics.md +365 -0
- package/data/database/design-patterns.md +68 -0
- package/data/database/index.md +8 -0
- package/data/database/indexing.md +136 -0
- package/data/database/nosql.md +223 -0
- package/data/database/schema.md +137 -0
- package/data/devops/ci-cd.md +66 -0
- package/data/devops/index.md +8 -0
- package/data/devops/observability.md +73 -0
- package/data/devops/practices.md +77 -0
- package/data/error-handling/basics.md +222 -0
- package/data/error-handling/index.md +7 -0
- package/data/error-handling/strategy.md +185 -0
- package/data/guideline-mappings.yml +1077 -0
- package/data/index.md +3 -0
- package/data/language/csharp/basics.md +210 -0
- package/data/language/csharp/testing.md +252 -0
- package/data/language/go/basics.md +158 -0
- package/data/language/go/testing.md +192 -0
- package/data/language/index.md +14 -0
- package/data/language/java/basics.md +184 -0
- package/data/language/java/testing.md +273 -0
- package/data/language/javascript/basics.md +217 -0
- package/data/language/javascript/testing.md +269 -0
- package/data/language/python/async.md +100 -0
- package/data/language/python/basics.md +100 -0
- package/data/language/python/index.md +10 -0
- package/data/language/python/testing.md +125 -0
- package/data/language/python/types.md +99 -0
- package/data/language/ruby/basics.md +227 -0
- package/data/language/ruby/testing.md +267 -0
- package/data/language/rust/basics.md +175 -0
- package/data/language/rust/testing.md +219 -0
- package/data/language/typescript/async.md +103 -0
- package/data/language/typescript/basics.md +87 -0
- package/data/language/typescript/config.md +95 -0
- package/data/language/typescript/error-handling.md +98 -0
- package/data/language/typescript/generics.md +85 -0
- package/data/language/typescript/index.md +14 -0
- package/data/language/typescript/interfaces-types.md +83 -0
- package/data/language/typescript/performance.md +103 -0
- package/data/language/typescript/testing.md +98 -0
- package/data/patterns/base-patterns.md +105 -0
- package/data/patterns/concurrency.md +87 -0
- package/data/patterns/data-access.md +83 -0
- package/data/patterns/distribution.md +86 -0
- package/data/patterns/domain-logic.md +81 -0
- package/data/patterns/gof.md +109 -0
- package/data/patterns/index.md +12 -0
- package/data/performance/async.md +148 -0
- package/data/performance/basics.md +324 -0
- package/data/performance/caching-strategies.md +68 -0
- package/data/performance/caching.md +152 -0
- package/data/performance/index.md +8 -0
- package/data/practices/code-review.md +52 -0
- package/data/practices/documentation.md +260 -0
- package/data/practices/index.md +11 -0
- package/data/practices/planning.md +142 -0
- package/data/practices/refactoring.md +91 -0
- package/data/practices/version-control.md +55 -0
- package/data/security/auth-jwt.md +159 -0
- package/data/security/headers.md +143 -0
- package/data/security/index.md +10 -0
- package/data/security/injection.md +119 -0
- package/data/security/secrets.md +148 -0
- package/data/style/index.md +8 -0
- package/data/style/naming.md +136 -0
- package/data/style/organization.md +162 -0
- package/data/templates/agents/architecture-reviewer.md +88 -0
- package/data/templates/agents/guideline-checker.md +73 -0
- package/data/templates/agents/security-auditor.md +108 -0
- package/data/templates/antigravity/rules/architecture.md.hbs +5 -0
- package/data/templates/antigravity/rules/code-style.md.hbs +5 -0
- package/data/templates/antigravity/rules/language.md.hbs +5 -0
- package/data/templates/antigravity/rules/performance.md.hbs +5 -0
- package/data/templates/antigravity/rules/security.md.hbs +5 -0
- package/data/templates/antigravity/rules/testing.md.hbs +5 -0
- package/data/templates/antigravity/workflows/add-documentation.md.hbs +23 -0
- package/data/templates/antigravity/workflows/generate-integration-tests.md.hbs +17 -0
- package/data/templates/antigravity/workflows/generate-unit-tests.md.hbs +20 -0
- package/data/templates/antigravity/workflows/performance-audit.md.hbs +24 -0
- package/data/templates/antigravity/workflows/refactor-extract-module.md.hbs +17 -0
- package/data/templates/antigravity/workflows/security-audit.md.hbs +20 -0
- package/data/templates/hooks/formatting.json +26 -0
- package/data/templates/hooks/security.json +35 -0
- package/data/templates/hooks/testing.json +17 -0
- package/data/testing/basics.md +151 -0
- package/data/testing/index.md +9 -0
- package/data/testing/integration.md +159 -0
- package/data/testing/unit-fundamentals.md +128 -0
- package/data/testing/unit-mocking.md +116 -0
- package/data/version.json +49 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +46 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config/profiles.d.ts +4 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +30 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/settings.d.ts +7 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +7 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58489 -0
- package/dist/index.js.map +1 -0
- package/dist/models/guideline.d.ts +15 -0
- package/dist/models/guideline.d.ts.map +1 -0
- package/dist/models/guideline.js +2 -0
- package/dist/models/guideline.js.map +1 -0
- package/dist/models/preference.d.ts +9 -0
- package/dist/models/preference.d.ts.map +1 -0
- package/dist/models/preference.js +2 -0
- package/dist/models/preference.js.map +1 -0
- package/dist/models/profile.d.ts +9 -0
- package/dist/models/profile.d.ts.map +1 -0
- package/dist/models/profile.js +2 -0
- package/dist/models/profile.js.map +1 -0
- package/dist/models/project.d.ts +13 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +2 -0
- package/dist/models/project.js.map +1 -0
- package/dist/services/ai/anthropic.d.ts +7 -0
- package/dist/services/ai/anthropic.d.ts.map +1 -0
- package/dist/services/ai/anthropic.js +39 -0
- package/dist/services/ai/anthropic.js.map +1 -0
- package/dist/services/generator.d.ts +2 -0
- package/dist/services/generator.d.ts.map +1 -0
- package/dist/services/generator.js +4 -0
- package/dist/services/generator.js.map +1 -0
- package/dist/services/learner.d.ts +2 -0
- package/dist/services/learner.d.ts.map +1 -0
- package/dist/services/learner.js +4 -0
- package/dist/services/learner.js.map +1 -0
- package/dist/services/scanner.d.ts +3 -0
- package/dist/services/scanner.d.ts.map +1 -0
- package/dist/services/scanner.js +54 -0
- package/dist/services/scanner.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +27 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/file.d.ts +7 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +32 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +17 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +14 -0
- package/dist/utils/path.js.map +1 -0
- package/docs/planning/memory-lane.md +83 -0
- package/package.json +64 -0
- package/packaging/linux/aicgen.spec +23 -0
- package/packaging/linux/control +9 -0
- package/packaging/macos/scripts/postinstall +12 -0
- package/packaging/windows/setup.nsi +92 -0
- package/planning/BRANDING-SUMMARY.md +194 -0
- package/planning/BRANDING.md +174 -0
- package/planning/BUILD.md +186 -0
- package/planning/CHUNK-IMPLEMENTATION-PLAN.md +87 -0
- package/planning/CHUNK-TAXONOMY.md +375 -0
- package/planning/CHUNKS-COMPLETE.md +382 -0
- package/planning/DESIGN.md +313 -0
- package/planning/DYNAMIC-GUIDELINES-DESIGN.md +265 -0
- package/planning/ENTERPRISE-UX-COMPLETE.md +281 -0
- package/planning/IMPLEMENTATION-PLAN.md +20 -0
- package/planning/PHASE1-COMPLETE.md +211 -0
- package/planning/PHASE2-COMPLETE.md +350 -0
- package/planning/PHASE3-COMPLETE.md +399 -0
- package/planning/PHASE4-COMPLETE.md +361 -0
- package/planning/PHASE4.5-CHUNKS.md +462 -0
- package/planning/STRUCTURE.md +170 -0
- package/scripts/add-categories.ts +87 -0
- package/scripts/build-binary.ts +46 -0
- package/scripts/embed-data.ts +105 -0
- package/scripts/generate-version.ts +150 -0
- package/scripts/test-decompress.ts +27 -0
- package/scripts/test-extract.ts +31 -0
- package/src/__tests__/services/assistant-file-writer.test.ts +400 -0
- package/src/__tests__/services/guideline-loader.test.ts +281 -0
- package/src/__tests__/services/tarball-extraction.test.ts +125 -0
- package/src/commands/add-guideline.ts +296 -0
- package/src/commands/clear.ts +61 -0
- package/src/commands/guideline-selector.ts +123 -0
- package/src/commands/init.ts +645 -0
- package/src/commands/quick-add.ts +586 -0
- package/src/commands/remove-guideline.ts +152 -0
- package/src/commands/stats.ts +49 -0
- package/src/commands/update.ts +240 -0
- package/src/config.ts +82 -0
- package/src/embedded-data.ts +1492 -0
- package/src/index.ts +67 -0
- package/src/models/profile.ts +24 -0
- package/src/models/project.ts +43 -0
- package/src/services/assistant-file-writer.ts +612 -0
- package/src/services/config-generator.ts +150 -0
- package/src/services/config-manager.ts +70 -0
- package/src/services/data-source.ts +248 -0
- package/src/services/first-run-init.ts +148 -0
- package/src/services/guideline-loader.ts +311 -0
- package/src/services/hook-generator.ts +178 -0
- package/src/services/subagent-generator.ts +310 -0
- package/src/utils/banner.ts +66 -0
- package/src/utils/errors.ts +27 -0
- package/src/utils/file.ts +67 -0
- package/src/utils/formatting.ts +172 -0
- package/src/utils/logger.ts +89 -0
- package/src/utils/path.ts +17 -0
- package/src/utils/wizard-state.ts +132 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Python Testing with pytest
|
|
2
|
+
|
|
3
|
+
## Basic Test Structure
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import pytest
|
|
7
|
+
from myapp.services import UserService
|
|
8
|
+
|
|
9
|
+
class TestUserService:
|
|
10
|
+
def test_create_user_with_valid_data(self):
|
|
11
|
+
# Arrange
|
|
12
|
+
service = UserService()
|
|
13
|
+
user_data = {"email": "test@example.com", "name": "Test User"}
|
|
14
|
+
|
|
15
|
+
# Act
|
|
16
|
+
user = service.create_user(user_data)
|
|
17
|
+
|
|
18
|
+
# Assert
|
|
19
|
+
assert user.id is not None
|
|
20
|
+
assert user.email == "test@example.com"
|
|
21
|
+
|
|
22
|
+
def test_create_user_with_invalid_email_raises_error(self):
|
|
23
|
+
service = UserService()
|
|
24
|
+
|
|
25
|
+
with pytest.raises(ValidationError, match="Invalid email"):
|
|
26
|
+
service.create_user({"email": "invalid", "name": "Test"})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Fixtures
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pytest
|
|
33
|
+
from sqlalchemy import create_engine
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def db_session():
|
|
37
|
+
"""Provide a transactional database session."""
|
|
38
|
+
engine = create_engine("sqlite:///:memory:")
|
|
39
|
+
Session = sessionmaker(bind=engine)
|
|
40
|
+
session = Session()
|
|
41
|
+
|
|
42
|
+
yield session
|
|
43
|
+
|
|
44
|
+
session.rollback()
|
|
45
|
+
session.close()
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def user_service(db_session):
|
|
49
|
+
"""Provide UserService with test database."""
|
|
50
|
+
return UserService(db_session)
|
|
51
|
+
|
|
52
|
+
# Usage - fixtures injected automatically
|
|
53
|
+
def test_find_user(user_service, db_session):
|
|
54
|
+
user = User(email="test@example.com")
|
|
55
|
+
db_session.add(user)
|
|
56
|
+
db_session.commit()
|
|
57
|
+
|
|
58
|
+
found = user_service.find_by_email("test@example.com")
|
|
59
|
+
assert found.id == user.id
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Parametrized Tests
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
@pytest.mark.parametrize("age,expected", [
|
|
66
|
+
(17, False),
|
|
67
|
+
(18, True),
|
|
68
|
+
(21, True),
|
|
69
|
+
(0, False),
|
|
70
|
+
(-1, False),
|
|
71
|
+
])
|
|
72
|
+
def test_is_adult(age, expected):
|
|
73
|
+
assert is_adult(age) == expected
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize("email", [
|
|
76
|
+
"user@example.com",
|
|
77
|
+
"user.name@example.co.uk",
|
|
78
|
+
"user+tag@example.com",
|
|
79
|
+
])
|
|
80
|
+
def test_valid_emails(email):
|
|
81
|
+
assert validate_email(email) is True
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Mocking
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from unittest.mock import Mock, patch, AsyncMock
|
|
88
|
+
|
|
89
|
+
def test_send_notification(mocker):
|
|
90
|
+
# Mock external service
|
|
91
|
+
mock_email = mocker.patch("myapp.services.email_client")
|
|
92
|
+
mock_email.send.return_value = True
|
|
93
|
+
|
|
94
|
+
service = NotificationService()
|
|
95
|
+
result = service.send_welcome_email("user@example.com")
|
|
96
|
+
|
|
97
|
+
assert result is True
|
|
98
|
+
mock_email.send.assert_called_once_with(
|
|
99
|
+
to="user@example.com",
|
|
100
|
+
template="welcome"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Async mocking
|
|
104
|
+
@pytest.mark.asyncio
|
|
105
|
+
async def test_fetch_user(mocker):
|
|
106
|
+
mock_client = mocker.patch("myapp.api.http_client")
|
|
107
|
+
mock_client.get = AsyncMock(return_value={"id": 1, "name": "Test"})
|
|
108
|
+
|
|
109
|
+
user = await fetch_user(1)
|
|
110
|
+
assert user["name"] == "Test"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## pytest Configuration
|
|
114
|
+
|
|
115
|
+
```ini
|
|
116
|
+
# pytest.ini
|
|
117
|
+
[pytest]
|
|
118
|
+
testpaths = tests
|
|
119
|
+
python_files = test_*.py
|
|
120
|
+
python_functions = test_*
|
|
121
|
+
addopts = -v --tb=short --strict-markers
|
|
122
|
+
markers =
|
|
123
|
+
slow: marks tests as slow
|
|
124
|
+
integration: marks tests as integration tests
|
|
125
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Python Type Hints
|
|
2
|
+
|
|
3
|
+
## Basic Type Annotations
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from typing import List, Dict, Optional, Union, Tuple
|
|
7
|
+
|
|
8
|
+
# Variable annotations
|
|
9
|
+
name: str = "Alice"
|
|
10
|
+
age: int = 30
|
|
11
|
+
prices: List[float] = [9.99, 19.99, 29.99]
|
|
12
|
+
user_scores: Dict[str, int] = {"alice": 100, "bob": 85}
|
|
13
|
+
|
|
14
|
+
# Function annotations
|
|
15
|
+
def greet(name: str, times: int = 1) -> str:
|
|
16
|
+
return f"Hello, {name}! " * times
|
|
17
|
+
|
|
18
|
+
def find_user(user_id: int) -> Optional[User]:
|
|
19
|
+
"""Returns User or None if not found."""
|
|
20
|
+
return db.get(user_id)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Modern Python 3.10+ Syntax
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
# ✅ Use built-in types directly (Python 3.9+)
|
|
27
|
+
def process(items: list[str]) -> dict[str, int]:
|
|
28
|
+
return {item: len(item) for item in items}
|
|
29
|
+
|
|
30
|
+
# ✅ Union syntax with | (Python 3.10+)
|
|
31
|
+
def parse(value: str | int | None) -> str:
|
|
32
|
+
if value is None:
|
|
33
|
+
return ""
|
|
34
|
+
return str(value)
|
|
35
|
+
|
|
36
|
+
# Instead of:
|
|
37
|
+
from typing import Union, Optional
|
|
38
|
+
def parse(value: Union[str, int, None]) -> str: ...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## TypedDict and Protocols
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from typing import TypedDict, Protocol
|
|
45
|
+
|
|
46
|
+
# ✅ TypedDict for structured dictionaries
|
|
47
|
+
class UserDict(TypedDict):
|
|
48
|
+
id: int
|
|
49
|
+
name: str
|
|
50
|
+
email: str
|
|
51
|
+
is_active: bool
|
|
52
|
+
|
|
53
|
+
def create_user(data: UserDict) -> User:
|
|
54
|
+
return User(**data)
|
|
55
|
+
|
|
56
|
+
# ✅ Protocol for structural typing (duck typing)
|
|
57
|
+
class Readable(Protocol):
|
|
58
|
+
def read(self) -> str: ...
|
|
59
|
+
|
|
60
|
+
def process_file(file: Readable) -> None:
|
|
61
|
+
content = file.read()
|
|
62
|
+
# Works with any object that has read() method
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Generics
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from typing import TypeVar, Generic
|
|
69
|
+
|
|
70
|
+
T = TypeVar('T')
|
|
71
|
+
|
|
72
|
+
class Repository(Generic[T]):
|
|
73
|
+
def __init__(self, model: type[T]) -> None:
|
|
74
|
+
self.model = model
|
|
75
|
+
|
|
76
|
+
def find(self, id: int) -> T | None:
|
|
77
|
+
return self.db.get(self.model, id)
|
|
78
|
+
|
|
79
|
+
def save(self, entity: T) -> T:
|
|
80
|
+
return self.db.save(entity)
|
|
81
|
+
|
|
82
|
+
# Usage
|
|
83
|
+
user_repo = Repository[User](User)
|
|
84
|
+
user = user_repo.find(1) # Returns User | None
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Type Checking with mypy
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Run type checker
|
|
91
|
+
mypy src/
|
|
92
|
+
|
|
93
|
+
# mypy.ini configuration
|
|
94
|
+
[mypy]
|
|
95
|
+
python_version = 3.11
|
|
96
|
+
strict = True
|
|
97
|
+
warn_return_any = True
|
|
98
|
+
warn_unused_ignores = True
|
|
99
|
+
```
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Ruby Fundamentals
|
|
2
|
+
|
|
3
|
+
## Project Structure (Rails)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
myapp/
|
|
7
|
+
├── app/
|
|
8
|
+
│ ├── controllers/
|
|
9
|
+
│ ├── models/
|
|
10
|
+
│ ├── services/
|
|
11
|
+
│ ├── views/
|
|
12
|
+
│ └── jobs/
|
|
13
|
+
├── config/
|
|
14
|
+
├── db/
|
|
15
|
+
│ └── migrate/
|
|
16
|
+
├── lib/
|
|
17
|
+
├── spec/ or test/
|
|
18
|
+
├── Gemfile
|
|
19
|
+
└── Gemfile.lock
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Naming Conventions
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Classes/Modules: PascalCase
|
|
26
|
+
class UserService
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module Authentication
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Methods/variables: snake_case
|
|
33
|
+
def create_user(email)
|
|
34
|
+
user_name = "test"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Constants: SCREAMING_SNAKE_CASE
|
|
38
|
+
MAX_RETRIES = 3
|
|
39
|
+
DEFAULT_TIMEOUT = 30
|
|
40
|
+
|
|
41
|
+
# Predicate methods: end with ?
|
|
42
|
+
def valid?
|
|
43
|
+
@errors.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Dangerous methods: end with !
|
|
47
|
+
def save!
|
|
48
|
+
raise Error unless save
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Private attr: prefix with _
|
|
52
|
+
attr_reader :_internal_state
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Idiomatic Ruby
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Blocks
|
|
59
|
+
users.each do |user|
|
|
60
|
+
puts user.name
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Short blocks with &:method
|
|
64
|
+
emails = users.map(&:email)
|
|
65
|
+
active = users.select(&:active?)
|
|
66
|
+
|
|
67
|
+
# Safe navigation
|
|
68
|
+
user&.profile&.avatar_url
|
|
69
|
+
|
|
70
|
+
# Default values
|
|
71
|
+
def greet(name = "World")
|
|
72
|
+
"Hello, #{name}!"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Keyword arguments
|
|
76
|
+
def create_user(email:, name: nil, role: :user)
|
|
77
|
+
User.new(email: email, name: name, role: role)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Multiple return values
|
|
81
|
+
def parse(input)
|
|
82
|
+
[result, errors]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
result, errors = parse(input)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Begin/rescue/ensure
|
|
92
|
+
begin
|
|
93
|
+
risky_operation
|
|
94
|
+
rescue NetworkError => e
|
|
95
|
+
logger.error("Network failed: #{e.message}")
|
|
96
|
+
retry if should_retry?
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
logger.error("Unexpected error: #{e.message}")
|
|
99
|
+
raise
|
|
100
|
+
ensure
|
|
101
|
+
cleanup
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Custom errors
|
|
105
|
+
class NotFoundError < StandardError
|
|
106
|
+
attr_reader :id
|
|
107
|
+
|
|
108
|
+
def initialize(id)
|
|
109
|
+
@id = id
|
|
110
|
+
super("Not found: #{id}")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Inline rescue (use sparingly)
|
|
115
|
+
value = risky_call rescue default_value
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Collections
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# Map/Select/Reduce
|
|
122
|
+
emails = users.map { |u| u.email }
|
|
123
|
+
active = users.select { |u| u.active? }
|
|
124
|
+
total = orders.reduce(0) { |sum, o| sum + o.total }
|
|
125
|
+
|
|
126
|
+
# Chaining
|
|
127
|
+
users
|
|
128
|
+
.select(&:active?)
|
|
129
|
+
.map(&:email)
|
|
130
|
+
.uniq
|
|
131
|
+
.sort
|
|
132
|
+
|
|
133
|
+
# Hash operations
|
|
134
|
+
counts = users.group_by(&:department)
|
|
135
|
+
.transform_values(&:count)
|
|
136
|
+
|
|
137
|
+
# Find
|
|
138
|
+
user = users.find { |u| u.id == target_id }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Classes and Modules
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Service object pattern
|
|
145
|
+
class CreateUser
|
|
146
|
+
def initialize(repository:, notifier:)
|
|
147
|
+
@repository = repository
|
|
148
|
+
@notifier = notifier
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def call(email:, name:)
|
|
152
|
+
user = User.new(email: email, name: name)
|
|
153
|
+
@repository.save(user)
|
|
154
|
+
@notifier.welcome(user)
|
|
155
|
+
user
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Module for shared behavior
|
|
160
|
+
module Timestampable
|
|
161
|
+
def created_at
|
|
162
|
+
@created_at ||= Time.now
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class User
|
|
167
|
+
include Timestampable
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Testing (RSpec)
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
RSpec.describe UserService do
|
|
175
|
+
let(:repository) { instance_double(UserRepository) }
|
|
176
|
+
let(:service) { described_class.new(repository: repository) }
|
|
177
|
+
|
|
178
|
+
describe "#create" do
|
|
179
|
+
context "with valid email" do
|
|
180
|
+
it "creates a user" do
|
|
181
|
+
allow(repository).to receive(:save)
|
|
182
|
+
|
|
183
|
+
user = service.create(email: "test@example.com")
|
|
184
|
+
|
|
185
|
+
expect(user.email).to eq("test@example.com")
|
|
186
|
+
expect(repository).to have_received(:save).with(user)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context "with invalid email" do
|
|
191
|
+
it "raises ValidationError" do
|
|
192
|
+
expect { service.create(email: "invalid") }
|
|
193
|
+
.to raise_error(ValidationError)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Rails Conventions
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
# Controller
|
|
204
|
+
class UsersController < ApplicationController
|
|
205
|
+
def create
|
|
206
|
+
@user = User.new(user_params)
|
|
207
|
+
if @user.save
|
|
208
|
+
redirect_to @user, notice: "User created"
|
|
209
|
+
else
|
|
210
|
+
render :new, status: :unprocessable_entity
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
def user_params
|
|
217
|
+
params.require(:user).permit(:email, :name)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Model
|
|
222
|
+
class User < ApplicationRecord
|
|
223
|
+
validates :email, presence: true, uniqueness: true
|
|
224
|
+
has_many :posts, dependent: :destroy
|
|
225
|
+
scope :active, -> { where(active: true) }
|
|
226
|
+
end
|
|
227
|
+
```
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Ruby Testing (RSpec)
|
|
2
|
+
|
|
3
|
+
## Project Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
myapp/
|
|
7
|
+
├── app/
|
|
8
|
+
│ └── services/
|
|
9
|
+
│ └── user_service.rb
|
|
10
|
+
└── spec/
|
|
11
|
+
├── spec_helper.rb
|
|
12
|
+
├── rails_helper.rb # Rails projects
|
|
13
|
+
└── services/
|
|
14
|
+
└── user_service_spec.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Basic Specs
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
RSpec.describe UserService do
|
|
21
|
+
describe "#create" do
|
|
22
|
+
it "creates a user with valid email" do
|
|
23
|
+
service = UserService.new
|
|
24
|
+
|
|
25
|
+
user = service.create(email: "test@example.com")
|
|
26
|
+
|
|
27
|
+
expect(user.email).to eq("test@example.com")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "raises error with invalid email" do
|
|
31
|
+
service = UserService.new
|
|
32
|
+
|
|
33
|
+
expect { service.create(email: "invalid") }
|
|
34
|
+
.to raise_error(ValidationError)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Let and Subject
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
RSpec.describe UserService do
|
|
44
|
+
subject(:service) { described_class.new(repository: repository) }
|
|
45
|
+
let(:repository) { instance_double(UserRepository) }
|
|
46
|
+
let(:user) { User.new(email: "test@example.com") }
|
|
47
|
+
|
|
48
|
+
describe "#find" do
|
|
49
|
+
before do
|
|
50
|
+
allow(repository).to receive(:find).with("1").and_return(user)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "returns the user" do
|
|
54
|
+
result = service.find("1")
|
|
55
|
+
expect(result).to eq(user)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Matchers
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# Equality
|
|
65
|
+
expect(actual).to eq(expected)
|
|
66
|
+
expect(actual).not_to eq(unexpected)
|
|
67
|
+
expect(actual).to eql(expected) # stricter equality
|
|
68
|
+
|
|
69
|
+
# Boolean
|
|
70
|
+
expect(value).to be true
|
|
71
|
+
expect(value).to be_truthy
|
|
72
|
+
expect(value).to be_falsy
|
|
73
|
+
expect(value).to be_nil
|
|
74
|
+
|
|
75
|
+
# Comparisons
|
|
76
|
+
expect(value).to be > 5
|
|
77
|
+
expect(value).to be_between(1, 10)
|
|
78
|
+
|
|
79
|
+
# Collections
|
|
80
|
+
expect(array).to include(item)
|
|
81
|
+
expect(array).to contain_exactly(1, 2, 3)
|
|
82
|
+
expect(array).to match_array([3, 1, 2])
|
|
83
|
+
expect(array).to be_empty
|
|
84
|
+
expect(hash).to have_key(:name)
|
|
85
|
+
|
|
86
|
+
# Strings
|
|
87
|
+
expect(string).to start_with("Hello")
|
|
88
|
+
expect(string).to end_with("World")
|
|
89
|
+
expect(string).to match(/pattern/)
|
|
90
|
+
|
|
91
|
+
# Types
|
|
92
|
+
expect(object).to be_a(User)
|
|
93
|
+
expect(object).to be_an_instance_of(User)
|
|
94
|
+
|
|
95
|
+
# Predicates (calls object.active?)
|
|
96
|
+
expect(user).to be_active
|
|
97
|
+
expect(user).to have_orders # calls has_orders?
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Contexts and Shared Examples
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
RSpec.describe UserService do
|
|
104
|
+
describe "#create" do
|
|
105
|
+
context "with valid email" do
|
|
106
|
+
it "creates the user" do
|
|
107
|
+
# test
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "sends welcome email" do
|
|
111
|
+
# test
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "with invalid email" do
|
|
116
|
+
it "raises ValidationError" do
|
|
117
|
+
# test
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Shared examples
|
|
124
|
+
RSpec.shared_examples "a persisted entity" do
|
|
125
|
+
it "has an id" do
|
|
126
|
+
expect(entity.id).not_to be_nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "has timestamps" do
|
|
130
|
+
expect(entity.created_at).not_to be_nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
RSpec.describe User do
|
|
135
|
+
let(:entity) { User.create(email: "test@example.com") }
|
|
136
|
+
|
|
137
|
+
it_behaves_like "a persisted entity"
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Mocking and Stubbing
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
RSpec.describe UserService do
|
|
145
|
+
let(:repository) { instance_double(UserRepository) }
|
|
146
|
+
let(:notifier) { instance_double(EmailNotifier) }
|
|
147
|
+
let(:service) { described_class.new(repository: repository, notifier: notifier) }
|
|
148
|
+
|
|
149
|
+
describe "#create" do
|
|
150
|
+
it "saves user and sends notification" do
|
|
151
|
+
user = User.new(email: "test@example.com")
|
|
152
|
+
|
|
153
|
+
# Stubbing
|
|
154
|
+
allow(repository).to receive(:save).and_return(user)
|
|
155
|
+
allow(notifier).to receive(:welcome)
|
|
156
|
+
|
|
157
|
+
result = service.create(email: "test@example.com")
|
|
158
|
+
|
|
159
|
+
# Verification
|
|
160
|
+
expect(repository).to have_received(:save).with(an_instance_of(User))
|
|
161
|
+
expect(notifier).to have_received(:welcome).with(user)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "raises when repository fails" do
|
|
165
|
+
allow(repository).to receive(:save).and_raise(DatabaseError)
|
|
166
|
+
|
|
167
|
+
expect { service.create(email: "test@example.com") }
|
|
168
|
+
.to raise_error(DatabaseError)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Partial doubles (real objects with stubbed methods)
|
|
174
|
+
RSpec.describe User do
|
|
175
|
+
it "can stub specific methods" do
|
|
176
|
+
user = User.new(email: "test@example.com")
|
|
177
|
+
allow(user).to receive(:premium?).and_return(true)
|
|
178
|
+
|
|
179
|
+
expect(user.premium?).to be true
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Before/After Hooks
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
RSpec.describe UserService do
|
|
188
|
+
before(:all) do
|
|
189
|
+
# Run once before all examples
|
|
190
|
+
DatabaseCleaner.strategy = :transaction
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
before(:each) do
|
|
194
|
+
# Run before each example
|
|
195
|
+
DatabaseCleaner.start
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
after(:each) do
|
|
199
|
+
# Run after each example
|
|
200
|
+
DatabaseCleaner.clean
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
around(:each) do |example|
|
|
204
|
+
# Wrap each example
|
|
205
|
+
Timecop.freeze(Time.local(2024)) do
|
|
206
|
+
example.run
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Testing Rails Controllers
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
RSpec.describe UsersController, type: :controller do
|
|
216
|
+
describe "POST #create" do
|
|
217
|
+
context "with valid params" do
|
|
218
|
+
it "creates a new user" do
|
|
219
|
+
expect {
|
|
220
|
+
post :create, params: { user: { email: "test@example.com" } }
|
|
221
|
+
}.to change(User, :count).by(1)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "redirects to the user" do
|
|
225
|
+
post :create, params: { user: { email: "test@example.com" } }
|
|
226
|
+
expect(response).to redirect_to(User.last)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Request Specs (API Testing)
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
RSpec.describe "Users API", type: :request do
|
|
237
|
+
describe "GET /api/users/:id" do
|
|
238
|
+
let(:user) { User.create(email: "test@example.com") }
|
|
239
|
+
|
|
240
|
+
it "returns the user" do
|
|
241
|
+
get "/api/users/#{user.id}"
|
|
242
|
+
|
|
243
|
+
expect(response).to have_http_status(:ok)
|
|
244
|
+
expect(JSON.parse(response.body)["email"]).to eq("test@example.com")
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Running Tests
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Run all specs
|
|
254
|
+
bundle exec rspec
|
|
255
|
+
|
|
256
|
+
# Run specific file
|
|
257
|
+
bundle exec rspec spec/services/user_service_spec.rb
|
|
258
|
+
|
|
259
|
+
# Run specific example
|
|
260
|
+
bundle exec rspec spec/services/user_service_spec.rb:15
|
|
261
|
+
|
|
262
|
+
# Run with tag
|
|
263
|
+
bundle exec rspec --tag integration
|
|
264
|
+
|
|
265
|
+
# Run with format
|
|
266
|
+
bundle exec rspec --format documentation
|
|
267
|
+
```
|