Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion analyzer/test_anonymous_trial.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_anon_grade_page_shows_trial_banner(self):
"""GET /grade/ for anon shows the trial banner and form."""
response = self.client.get(reverse("grade_query"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Trial mode")
self.assertContains(response, "free grades left")
self.assertContains(response, "SQL Query Grader")

# ---------- POST /grade/ ----------
Expand Down
2 changes: 1 addition & 1 deletion analyzer/test_database_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def test_database_analyze_get(self):
"""Test database analyze view GET request."""
response = self.client.get(reverse("database_analyze"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Database Architecture Analysis")
self.assertContains(response, "Connect a database")
self.assertContains(response, "Database Engine")

def test_database_analyze_requires_login(self):
Expand Down
15 changes: 9 additions & 6 deletions analyzer/test_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def test_feedback_form_display(self):
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "Provide Feedback")
self.assertContains(response, "Provide feedback")
self.assertContains(response, "How accurate was the analysis?")
self.assertContains(response, "How useful were the recommendations?")
self.assertContains(response, "How clear was the feedback?")
Expand Down Expand Up @@ -205,7 +205,7 @@ def test_feedback_form_prepopulation(self):
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "Update Your Feedback")
self.assertContains(response, "Update your feedback")
# Check form has existing values (this is a basic check)
self.assertContains(response, "Existing feedback")

Expand Down Expand Up @@ -288,11 +288,11 @@ def test_feedback_analytics_display(self):
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "Feedback Analytics")
self.assertContains(response, "Feedback analytics")
# Template shows "Feedback will appear here" when no aggregated stats available
# The analytics view may require minimum feedback threshold
# Just verify page renders successfully
self.assertIn("Feedback Analytics", response.content.decode())
self.assertIn("Feedback analytics", response.content.decode())

def test_feedback_analytics_no_data(self):
"""Test feedback analytics page with no feedback data."""
Expand All @@ -306,7 +306,8 @@ def test_feedback_analytics_no_data(self):
self.assertEqual(response.status_code, 200)
# Template shows "Feedback will appear here once users start providing ratings"
self.assertContains(
response, "Feedback will appear here once users start providing ratings"
response,
"Feedback analytics will populate as users rate their analyses",
)

def test_feedback_form_validation(self):
Expand All @@ -330,7 +331,9 @@ def test_feedback_button_in_results(self):
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertContains(response, "Provide Feedback")
# The detailed-feedback CTA links to submit_feedback; the visible label
# is "Detailed feedback" (was "Provide Feedback" pre-UX-pass).
self.assertContains(response, "Detailed feedback")
self.assertContains(
response, reverse("submit_feedback", args=[self.analysis.id])
)
Expand Down
42 changes: 21 additions & 21 deletions analyzer/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,13 @@ def test_full_query_grading_workflow(self):
results_response, f"{analysis.grade}"
) # Grade letter is displayed
self.assertContains(results_response, f"{analysis.score}") # Score is displayed
self.assertContains(results_response, "Query Analysis Results")
# Page title was retitled to "Grade results" in the UX pass.
self.assertContains(results_response, "Grade results")

# Step 6: Check query history page
history_response = self.client.get(reverse("query_history"))
self.assertEqual(history_response.status_code, 200)
self.assertContains(history_response, "Query History")
self.assertContains(history_response, "Query history")
self.assertContains(history_response, analysis.grade)
self.assertContains(history_response, "SELECT")

Expand Down Expand Up @@ -211,28 +212,24 @@ def test_poor_query_grading_workflow(self):
self.assertContains(results_response, "Recommendations")

def test_authentication_required(self):
"""Test that authentication is required for grading pages."""
"""Test that authentication is required for protected pages.

Note: the grade form itself is no longer login-gated — anonymous users
can grade up to ANON_TRIAL_CAP queries per session. Pages that remain
login-gated are the user-scoped ones (history, account, connections).
"""

# Logout first since setUp force_login's the user
self.client.logout()

# Try to access grade query page without login
# /grade/ is anonymously accessible (trial flow)
grade_response = self.client.get(reverse("grade_query"))
self.assertEqual(grade_response.status_code, 302) # Redirect to login
self.assertEqual(grade_response.status_code, 200)

# Try to access history page without login
# History page still requires login (it's user-scoped)
history_response = self.client.get(reverse("query_history"))
self.assertEqual(history_response.status_code, 302) # Redirect to login

# Create an analysis to test results page
self.client.login(username="integrationuser", password="testpass123")
self.client.post(reverse("grade_query"), {"sql_query": "SELECT * FROM users;"})
analysis = QueryAnalysis.objects.first()
self.client.logout()

# Try to access results page without login
results_response = self.client.get(reverse("grade_results", args=[analysis.id]))
self.assertEqual(results_response.status_code, 302) # Redirect to login
self.assertEqual(history_response.status_code, 302)
self.assertTrue(history_response.url.startswith("/login/"))

def test_invalid_query_handling(self):
"""Test handling of invalid SQL queries."""
Expand Down Expand Up @@ -380,10 +377,13 @@ def test_grade_display_formatting(self):
analysis = QueryAnalysis.objects.first()
results_response = self.client.get(reverse("grade_results", args=[analysis.id]))

# Check for grade badge and score display
self.assertContains(results_response, f"grade-{analysis.grade.lower()}")
# The grade-{letter} CSS class was retired in the UX pass; the grade
# pill is now styled via Tailwind utilities (bg-emerald-50 / bg-lime-50
# / bg-amber-50 / bg-orange-50 / bg-red-50). Assert the visible grade
# letter and the formatted score directly.
self.assertContains(results_response, analysis.grade)
self.assertContains(results_response, f"{analysis.score:.1f}")

# Check history page formatting
# History page formatting — assert grade letter appears for this row
history_response = self.client.get(reverse("query_history"))
self.assertContains(history_response, f"grade-{analysis.grade.lower()}")
self.assertContains(history_response, analysis.grade)
10 changes: 6 additions & 4 deletions analyzer/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,13 @@ def test_optimization_integration_workflow(self):
results_response = self.client.get(reverse("grade_results", args=[analysis.id]))
self.assertEqual(results_response.status_code, 200)

# Check that optimization section is present if there are issues
# Check that optimization section is present if there are issues.
# Heading / tab labels were lowercased + shortened in the UX pass; assert
# the current strings rather than the pre-pass title-cased versions.
if len(analysis.issues_found) > 0:
self.assertContains(results_response, "Query Optimization Suggestions")
self.assertContains(results_response, "Optimized Query")
self.assertContains(results_response, "Side-by-Side Comparison")
self.assertContains(results_response, "Optimization suggestions")
self.assertContains(results_response, "Optimized")
self.assertContains(results_response, "Side-by-side")
self.assertContains(results_response, "Explanations")

def test_optimization_with_no_issues(self):
Expand Down
7 changes: 5 additions & 2 deletions analyzer/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@

class ParserTestCase(TestCase):
def setUp(self):
# Sample MySQL logs live at the repo-root `samples/` dir, not under
# `analyzer/samples/` (no such directory exists).
repo_root = os.path.dirname(os.path.dirname(__file__))
self.sample_slow_log_path = os.path.join(
os.path.dirname(__file__), "samples", "mysql-slow-query.log"
repo_root, "samples", "mysql-slow-query.log"
)
self.sample_general_log_path = os.path.join(
os.path.dirname(__file__), "samples", "mysql-general-query.log"
repo_root, "samples", "mysql-general-query.log"
)

def test_parse_mysql_slow_log(self):
Expand Down
Loading