Skip to content

Commit 3bf2a71

Browse files
committed
unit test completed
1 parent 43b7bad commit 3bf2a71

File tree

47 files changed

+1695
-49
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1695
-49
lines changed

backend/schemas/dashboard_dto.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
from pydantic import BaseModel
1+
from pydantic import BaseModel, field_validator, ValidationError
2+
23

34
class DashboardStatsResponse(BaseModel):
45
policies: int
56
alerts: int
67
security_score: int
78

9+
@field_validator('policies','alerts','security_score')
10+
@classmethod
11+
def ensure_not_negative(cls, v):
12+
if v < 0:
13+
raise ValueError(f'Policies value {v} is negative')
14+
return v
15+
16+
817
class DashboardErrorResponse(BaseModel):
918
error: str

backend/schemas/full_scan_dto.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pydantic import BaseModel
1+
from pydantic import BaseModel, Field
22
from typing import Dict, Optional, Any
33

44
class T5Correction(BaseModel):
@@ -7,13 +7,13 @@ class T5Correction(BaseModel):
77
error: Optional[str] = None
88

99
class FullScanResponse(BaseModel):
10-
checkov: Dict[str, Any] # Includes scan_id and results or error
10+
checkov: Dict[str, Any]
1111
t5: Dict[str, T5Correction]
12-
checkov_corrected: Dict[str, Any] # Includes scan_id and results or error
13-
semgrep: Dict[str, Any] # SemgrepScanResponse or error
12+
checkov_corrected: Dict[str, Any]
13+
semgrep: Dict[str, Any]
1414

1515
class FullScanRequest(BaseModel):
16-
repo_url: str
16+
repo_url: str=Field(...,min_length=20,description="The full repository URL")
1717

1818
class FullScanErrorResponse(BaseModel):
1919
error: str

backend/schemas/history_dto.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
from pydantic import BaseModel
1+
from pydantic import BaseModel, field_validator
22
from typing import Optional, Dict, Any
33

44
class ScanHistoryResponse(BaseModel):
55
id: int
6-
repo_id: Optional[int]
6+
repo_id: Optional[int] = None
77
scan_result: Dict[str, Any]
8-
repo_url: Optional[str]
9-
item_name: Optional[str]
10-
status: Optional[str]
11-
score: Optional[int]
12-
compliant: Optional[bool]
8+
repo_url: Optional[str] = None
9+
item_name: Optional[str] = None
10+
status: Optional[str] = None
11+
score: Optional[int] = None
12+
compliant: Optional[bool] = None
1313
created_at: str
14-
input_type: Optional[str]
15-
scan_type: Optional[str]
14+
input_type: Optional[str] = None
15+
scan_type: Optional[str] = None
16+
17+
@field_validator('id',mode='before')
18+
def validate_id(cls, v):
19+
if v < 0:
20+
raise ValueError("Id must be positive")
21+
return v
1622

1723
class HistoryErrorResponse(BaseModel):
1824
error: str

backend/services/full_scan_service.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ def run_full_scan(user_id, repo_url, jwt_token):
136136

137137
# Step 4: Semgrep scan
138138
try:
139-
# ⚠️ on utilise la version de run_semgrep fournie par ton service.
140-
# Elle gère déjà la sauvegarde + notifs + email CSV (selon ta version actuelle).
141139
semgrep_result = run_semgrep(
142140
user_id=user_id,
143141
input_type="repo",

backend/services/github_service.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ def github_callback(code):
121121

122122
def get_github_repos(user_id):
123123
"""Fetch GitHub repositories for a user."""
124+
github_user = GithubUser.query.filter_by(user_id=user_id).first()
125+
if not github_user:
126+
logger.error(f"No GitHub account linked for user_id {user_id}")
127+
raise ValueError("Compte GitHub non lié")
128+
access_token = github_user.access_token
124129
try:
125-
github_user = GithubUser.query.filter_by(user_id=user_id).first()
126-
if not github_user:
127-
logger.error(f"No GitHub account linked for user_id {user_id}")
128-
raise ValueError("Compte GitHub non lié")
129-
access_token = github_user.access_token
130130

131131
headers = {"Authorization": f"Bearer {access_token}"}
132132
response = requests.get("https://api.github.com/user/repos", headers=headers)
@@ -234,11 +234,12 @@ def save_selected_repos(user_id, selected_repos):
234234

235235
def get_repo_configs(user_id):
236236
"""Fetch and store configuration files from selected repositories."""
237+
github_user = GithubUser.query.filter_by(user_id=user_id).first()
238+
if not github_user:
239+
logger.error(f"No GitHub account linked for user_id {user_id}")
240+
raise ValueError("Compte GitHub non lié")
237241
try:
238-
github_user = GithubUser.query.filter_by(user_id=user_id).first()
239-
if not github_user:
240-
logger.error(f"No GitHub account linked for user_id {user_id}")
241-
raise ValueError("Compte GitHub non lié")
242+
242243
access_token = github_user.access_token
243244

244245
repos = SelectedRepo.query.filter_by(user_id=user_id).all()

backend/services/notification_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from utils.db import db
1010
from models.user import User
1111
from models.notification import Notification
12-
from models.user_preference import UserPreference
1312

1413
# On réutilise tes identifiants SMTP déjà définis dans services/email_template.py
1514
from services.email_template import EMAIL_ADDRESS, EMAIL_PASSWORD

backend/services/report_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import csv
44
import tempfile
5-
from datetime import datetime
5+
from datetime import datetime, timezone
66
from typing import List, Dict, Any, Tuple
77
import smtplib
88
from email.message import EmailMessage
@@ -138,7 +138,7 @@ def generate_csv_for_scan(scan_id: int) -> Tuple[str, str]:
138138
})
139139

140140
repo_part = (scan.repo_url or "local").rstrip("/").split("/")[-1]
141-
ts = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
141+
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
142142
filename = f"safeops_report_{scan.scan_type}_{repo_part}_{scan.id}_{ts}.csv"
143143
file_path = os.path.join(tempfile.gettempdir(), filename)
144144

@@ -225,7 +225,7 @@ def send_csv_report_email(
225225
<p>Thank you for using <strong>SafeOps+</strong>!</p>
226226
</div>
227227
<div class="footer">
228-
&copy; {datetime.utcnow().year} SafeOps+ — Automated Security Platform
228+
&copy; {datetime.now(timezone.utc).year} SafeOps+ — Automated Security Platform
229229
</div>
230230
</div>
231231
</body>

backend/services/semgrep_service.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# services/semgrep_service.py
21
from pathlib import Path
32
import os
43
import tempfile
@@ -19,9 +18,7 @@
1918
# Email + CSV
2019
from services.report_service import generate_csv_for_scan, send_csv_report_email
2120

22-
# -------------------------------------------------------------------
23-
# Logging / Config
24-
# -------------------------------------------------------------------
21+
2522
logging.basicConfig(
2623
level=logging.DEBUG,
2724
format='%(asctime)s %(levelname)s:%(name)s: %(message)s',
@@ -36,9 +33,6 @@
3633
genai.configure(api_key=GEMINI_API_KEY)
3734

3835

39-
# -------------------------------------------------------------------
40-
# Helpers
41-
# -------------------------------------------------------------------
4236
def clean_path(full_path: str) -> str:
4337
"""Remove temp-dir prefixes from file paths for nicer display."""
4438
try:
@@ -73,9 +67,7 @@ def get_gemini_suggestion(finding: dict, max_retries: int = 3, delay: int = 2) -
7367
return "Review this finding and apply best practices."
7468

7569

76-
# -------------------------------------------------------------------
77-
# Semgrep execution
78-
# -------------------------------------------------------------------
70+
7971
def run_semgrep(target_path: str) -> dict:
8072
"""Run Semgrep and return normalized results (failed list + summary)."""
8173
cmd = ["semgrep", "scan", target_path, "--config=auto", "--json"]
@@ -133,9 +125,7 @@ def run_semgrep(target_path: str) -> dict:
133125
raise RuntimeError(f"Semgrep failed: {str(e)}")
134126

135127

136-
# -------------------------------------------------------------------
137-
# Persistence + Email (one HTML email with CSV; English; no emojis; no scan id)
138-
# -------------------------------------------------------------------
128+
139129
def save_scan_history(user_id, result, input_type, repo_url=None):
140130
"""Persist Semgrep results and send one HTML email (with CSV) — English, no emojis, no scan id in subject."""
141131
try:
@@ -179,10 +169,10 @@ def save_scan_history(user_id, result, input_type, repo_url=None):
179169
send_csv_report_email(
180170
to_email=user.email,
181171
subject=subject,
182-
body_text=body, # HTML-friendly; wrapped in blue template
172+
body_text=body,
183173
csv_path=csv_path,
184174
csv_filename=csv_filename,
185-
user_name=user.name, # greet by name
175+
user_name=user.name,
186176
)
187177
except Exception as e:
188178
logger.warning(f"Combined email (finish + CSV) failed for Semgrep scan_id {scan_id}: {e}")
@@ -194,10 +184,6 @@ def save_scan_history(user_id, result, input_type, repo_url=None):
194184
db.session.rollback()
195185
raise RuntimeError(f"Failed to save scan: {str(e)}")
196186

197-
198-
# -------------------------------------------------------------------
199-
# Entry point used by route
200-
# -------------------------------------------------------------------
201187
def validate_semgrep(user_id, input_type, file=None, repo_url=None, content=None, extension="py"):
202188
"""
203189
Validate inputs and run Semgrep. Returns {"scan_id": id, **result}.

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
from fixtures.admin_fixtures import *
1212
from fixtures.checkov_fixtures import *
1313
from fixtures.semgrep_fixtures import *
14+
from fixtures.dashboard_fixtures import *
15+
from fixtures.fullscan_fixtures import *
16+
from fixtures.history_fixtures import *
17+
from fixtures.notification_fixtures import *
18+
from fixtures.report_fixtures import *
19+
from fixtures.github_fixtures import *
20+
from fixtures.google_fixtures import *
1421

1522

1623

1.19 KB
Binary file not shown.

0 commit comments

Comments
 (0)