Skip to content

Commit 05e6038

Browse files
author
RobuRishabh
committed
tests_updated_project_complete
1 parent 7d579d5 commit 05e6038

File tree

4 files changed

+288
-175
lines changed

4 files changed

+288
-175
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,27 @@
44
The RAG-based Flight Query Bot is a Streamlit-powered web application that allows users to query flight information using natural language (e.g., "Show me flights from New York to London"). It uses Retrieval-Augmented Generation (RAG) with an Ollama language model to extract entities from queries and retrieve flight data from a mock database. The project is deployed on a local Kubernetes cluster using Minikube and includes a CI/CD pipeline with GitHub Actions for automated testing.
55

66
### Architecture
7+
```
8+
RAG-based-Flight-Query-Bot/
9+
├── .github/
10+
│ └── workflows/
11+
│ └── test.yml
12+
├── tests/
13+
│ └── test_mock_database.py
14+
├── mock_database.py
15+
├── query_handler.py
16+
├── ollama_api.py
17+
├── app.py (optional)
18+
├── requirements.txt
19+
└── README.md
20+
```
721
- **Frontend**: Streamlit UI (`app.py`) for user interaction.
822
- **Backend**:
923
- `query_handler.py`: Processes queries and extracts entities using Ollama.
1024
- `ollama_api.py`: Integrates with the Ollama LLM for natural language responses.
1125
- `mock_database.py`: Provides mock flight data and search functionality.
1226
- **Deployment**: Kubernetes on Minikube with two services: `flight-assistant-service` (Streamlit) and `ollama-service` (Ollama server).
1327
- **CI/CD**: GitHub Actions runs unit tests on every push or pull request.
14-
1528
---
1629

1730
## Setup Instructions

tests/test_mock_databse.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
11
import unittest
2+
from unittest.mock import patch
3+
import os
4+
import requests
25
from mock_database import flights, search_flights, check_ollama_availability
36

47
class TestMockDatabase(unittest.TestCase):
58
def test_flight_data_exists(self):
69
self.assertGreater(len(flights), 0, "Flight database should not be empty")
710

811
def test_flight_structure(self):
9-
flight = flights[0]
12+
self.assertEqual(len(flights), 5, "Expected exactly 5 flights")
1013
expected_keys = {"flight_number", "origin", "destination", "time", "airline"}
11-
self.assertEqual(set(flight.keys()), expected_keys, "Flight data has incorrect structure")
14+
for flight in flights:
15+
self.assertEqual(set(flight.keys()), expected_keys, "Flight data has incorrect structure")
16+
self.assertIsInstance(flight["flight_number"], str, "Flight number should be a string")
17+
self.assertNotEqual(flight["origin"], flight["destination"], "Origin and destination should differ")
1218

1319
def test_search_flights_by_flight_number(self):
14-
results = search_flights(flight_number="NY100") # Use keyword arg for flight_number
15-
self.assertGreater(len(results), 0, "Should find at least one matching flight")
16-
self.assertIn("NY100", [f["flight_number"] for f in results], "Flight NY100 should be found")
20+
results = search_flights(flight_number="NY100")
1721
self.assertEqual(len(results), 1, "Should find exactly one flight for NY100")
22+
self.assertEqual(results[0]["flight_number"], "NY100", "Flight NY100 should be found")
1823

1924
def test_search_flights_by_origin(self):
2025
results = search_flights(origin="New York")
21-
self.assertGreater(len(results), 0, "Should find flights from New York")
26+
self.assertEqual(len(results), 1, "Should find exactly one flight from New York")
2227
self.assertTrue(all(f["origin"] == "New York" for f in results), "All results should have origin New York")
2328

2429
def test_search_flights_by_destination(self):
2530
results = search_flights(destination="London")
26-
self.assertGreater(len(results), 0, "Should find flights to London")
31+
self.assertEqual(len(results), 1, "Should find exactly one flight to London")
2732
self.assertTrue(all(f["destination"] == "London" for f in results), "All results should have destination London")
2833

2934
def test_search_flights_by_airline(self):
3035
results = search_flights(airline="Global Airways")
31-
self.assertGreater(len(results), 0, "Should find flights by Global Airways")
36+
self.assertEqual(len(results), 1, "Should find exactly one flight by Global Airways")
3237
self.assertTrue(all(f["airline"] == "Global Airways" for f in results), "All results should have airline Global Airways")
3338

39+
def test_search_flights_combined_filters(self):
40+
results = search_flights(origin="San Francisco", destination="Sydney")
41+
self.assertEqual(len(results), 1, "Should find exactly one flight from San Francisco to Sydney")
42+
self.assertEqual(results[0]["flight_number"], "SF400", "Flight SF400 should be found")
43+
3444
def test_search_flights_no_results(self):
3545
results = search_flights(flight_number="XYZ999")
3646
self.assertEqual(len(results), 0, "Should return no results for invalid flight number")
@@ -41,8 +51,29 @@ def test_search_flights_no_parameters(self):
4151

4252
def test_search_flights_case_insensitivity(self):
4353
results = search_flights(flight_number="ny100")
44-
self.assertGreater(len(results), 0, "Should find NY100 regardless of case")
45-
self.assertIn("NY100", [f["flight_number"] for f in results], "Flight NY100 should be found")
54+
self.assertEqual(len(results), 1, "Should find NY100 regardless of case")
55+
self.assertEqual(results[0]["flight_number"], "NY100", "Flight NY100 should be found")
56+
57+
def test_search_flights_ignore_city_name(self):
58+
results = search_flights(destination="City Name")
59+
self.assertEqual(len(results), 0, "Should ignore 'City Name' as destination")
60+
61+
@patch("requests.get")
62+
def test_check_ollama_availability_success(self, mock_get):
63+
mock_response = mock_get.return_value
64+
mock_response.status_code = 200
65+
self.assertTrue(check_ollama_availability(), "Should return True when Ollama is available")
66+
67+
@patch("requests.get")
68+
def test_check_ollama_availability_failure(self, mock_get):
69+
mock_response = mock_get.return_value
70+
mock_response.status_code = 500
71+
self.assertFalse(check_ollama_availability(), "Should return False on server error")
72+
73+
@patch("requests.get")
74+
def test_check_ollama_availability_exception(self, mock_get):
75+
mock_get.side_effect = requests.RequestException("Connection error")
76+
self.assertFalse(check_ollama_availability(), "Should return False on exception")
4677

4778
if __name__ == "__main__":
4879
unittest.main()

tests/test_ollama_api.py

Lines changed: 96 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,109 @@
1-
import unittest
1+
import pytest
22
import requests
33
from unittest.mock import patch, Mock
4-
from ollama_api import initialize_ollama, check_ollama_availability, generate_response, generate_fallback_response
4+
import os
5+
from ollama_api import initialize_ollama, check_ollama_availability, generate_fallback_response, generate_response, ollama_llm
56

6-
class TestOllamaAPI(unittest.TestCase):
7-
def setUp(self):
8-
# Reset global ollama_llm for each test
9-
global ollama_llm
10-
ollama_llm = None
7+
# Fixture to mock environment variables
8+
@pytest.fixture
9+
def mock_env():
10+
original_env = os.environ.copy()
11+
os.environ["OLLAMA_URL"] = "http://test:11434"
12+
os.environ["OLLAMA_MODEL"] = "llama2:test"
13+
yield
14+
os.environ.clear()
15+
os.environ.update(original_env)
1116

12-
@patch("ollama_api.OllamaLLM")
13-
def test_initialize_ollama_success(self, mock_ollama_llm):
14-
mock_llm_instance = Mock()
15-
mock_ollama_llm.return_value = mock_llm_instance
16-
result = initialize_ollama()
17-
self.assertIsNotNone(result, "Ollama LLM should initialize successfully")
18-
self.assertEqual(result, mock_llm_instance)
17+
# 1. Tests for initialize_ollama
18+
@patch("ollama_api.OllamaLLM")
19+
def test_initialize_ollama_success(mock_ollama, mock_env):
20+
mock_instance = Mock()
21+
mock_ollama.return_value = mock_instance
22+
result = initialize_ollama()
23+
assert result == mock_instance, "Should return OllamaLLM instance on success"
24+
mock_ollama.assert_called_once_with(model="llama2:test", base_url="http://test:11434")
1925

20-
@patch("ollama_api.OllamaLLM", side_effect=Exception("Initialization failed"))
21-
def test_initialize_ollama_failure(self, mock_ollama_llm):
22-
result = initialize_ollama()
23-
self.assertIsNone(result, "Ollama LLM should return None on failure")
26+
@patch("ollama_api.OllamaLLM")
27+
def test_initialize_ollama_failure(mock_ollama, mock_env):
28+
mock_ollama.side_effect = Exception("Connection failed")
29+
result = initialize_ollama()
30+
assert result is None, "Should return None on initialization failure"
2431

25-
@patch("ollama_api.requests.get")
26-
def test_check_ollama_availability_success(self, mock_get):
27-
mock_response = Mock()
28-
mock_response.status_code = 200
29-
mock_get.return_value = mock_response
30-
is_available, message = check_ollama_availability()
31-
self.assertTrue(is_available, "Ollama should be available")
32-
self.assertIn("available", message)
32+
# 2. Tests for check_ollama_availability
33+
@patch("requests.get")
34+
def test_check_ollama_availability_success(mock_get, mock_env):
35+
mock_response = mock_get.return_value
36+
mock_response.status_code = 200
37+
is_available, message = check_ollama_availability()
38+
assert is_available is True, "Should return True when server is available"
39+
assert "Ollama server is available" in message, "Should return success message"
40+
mock_get.assert_called_once_with("http://test:11434/api/tags", timeout=3)
3341

34-
@patch("ollama_api.requests.get", side_effect=requests.RequestException("Connection error"))
35-
def test_check_ollama_availability_failure(self, mock_get):
36-
is_available, message = check_ollama_availability()
37-
self.assertFalse(is_available, "Ollama should not be available")
38-
self.assertIn("not available", message)
42+
@patch("requests.get")
43+
def test_check_ollama_availability_failure(mock_get, mock_env):
44+
mock_get.side_effect = requests.RequestException("Connection error")
45+
is_available, message = check_ollama_availability()
46+
assert is_available is False, "Should return False on request exception"
47+
assert "Ollama server is not available" in message, "Should return error message"
3948

40-
def test_generate_fallback_response_with_flights(self):
41-
flights = [
42-
{"flight_number": "NY100", "origin": "New York", "destination": "London", "time": "2025-05-01 08:00", "airline": "Global Airways"}
43-
]
44-
response = generate_fallback_response("test query", flights)
45-
self.assertIn("NY100", response)
46-
self.assertIn("New York", response)
47-
self.assertIn("London", response)
48-
self.assertIn("2025-05-01 08:00", response)
49-
self.assertIn("Global Airways", response)
49+
# 3. Tests for generate_fallback_response
50+
def test_generate_fallback_response_with_flights():
51+
flights = [
52+
{"flight_number": "NY100", "origin": "New York", "destination": "London", "time": "2025-05-01 08:00", "airline": "Global Airways"}
53+
]
54+
result = generate_fallback_response("flights from New York", flights)
55+
assert "Flight NY100" in result, "Should include flight number"
56+
assert "New York to London" in result, "Should include origin and destination"
57+
assert "Time: 2025-05-01 08:00" in result, "Should include time"
58+
assert "Airline: Global Airways" in result, "Should include airline"
5059

51-
def test_generate_fallback_response_no_flights(self):
52-
response = generate_fallback_response("test query", [])
53-
self.assertEqual(response, "I couldn't find any flights matching your criteria. Please try again.")
60+
def test_generate_fallback_response_no_flights():
61+
result = generate_fallback_response("flights from Mars", [])
62+
assert "I couldn't find any flights" in result, "Should return no-flights message"
5463

55-
@patch("ollama_api.check_ollama_availability")
56-
@patch("ollama_api.ollama_llm", new=Mock(invoke=lambda x: "Flight NY100 departs at 08:00 from New York to London with Global Airways"))
57-
def test_generate_response_success(self, mock_check_availability):
58-
mock_check_availability.return_value = (True, "Ollama available")
59-
query = "Show me flight NY100"
60-
flight_data = [
61-
{"flight_number": "NY100", "origin": "New York", "destination": "London", "time": "2025-05-01 08:00", "airline": "Global Airways"}
62-
]
63-
response = generate_response(query, flight_data)
64-
self.assertIn("NY100", response)
65-
self.assertIn("08:00", response)
66-
self.assertIn("New York", response)
67-
self.assertIn("London", response)
68-
self.assertIn("Global Airways", response)
64+
def test_generate_fallback_response_missing_fields():
65+
flights = [{"flight_number": "NY100"}] # Missing other fields
66+
result = generate_fallback_response("test query", flights)
67+
assert "Flight NY100" in result, "Should include flight number"
68+
assert "Unknown to Unknown" in result, "Should handle missing origin/destination"
69+
assert "Time: N/A" in result, "Should handle missing time"
70+
assert "Airline: N/A" in result, "Should handle missing airline"
6971

70-
@patch("ollama_api.check_ollama_availability")
71-
def test_generate_response_no_flights(self, mock_check_availability):
72-
mock_check_availability.return_value = (True, "Ollama available")
73-
# Ensure ollama_llm is initialized (mock it globally)
74-
global ollama_llm
75-
ollama_llm = Mock(invoke=lambda x: "No flights found.")
76-
query = "Show me flight XYZ999"
77-
flight_data = []
78-
response = generate_response(query, flight_data)
79-
self.assertEqual(response, "I couldn't find any flights matching your criteria. Please try again.")
72+
# 4. Tests for generate_response
73+
@patch("ollama_api.check_ollama_availability")
74+
@patch("ollama_api.ollama_llm.invoke")
75+
def test_generate_response_ollama_success(mock_invoke, mock_check, mock_env):
76+
mock_check.return_value = (True, "Server available")
77+
mock_invoke.return_value = "Flight NY100 departs from New York to London at 08:00 with Global Airways."
78+
with patch("ollama_api.ollama_llm", Mock()):
79+
flights = [{"flight_number": "NY100", "origin": "New York", "destination": "London", "time": "2025-05-01 08:00", "airline": "Global Airways"}]
80+
result = generate_response("flights from New York", flights)
81+
assert "NY100" in result, "Should include flight details from Ollama"
82+
assert "New York to London" in result, "Should include route"
83+
mock_invoke.assert_called_once()
8084

81-
@patch("ollama_api.check_ollama_availability")
82-
def test_generate_response_ollama_unavailable(self, mock_check_availability):
83-
mock_check_availability.return_value = (False, "Ollama not available")
84-
query = "Show me flight NY100"
85-
flight_data = [
86-
{"flight_number": "NY100", "origin": "New York", "destination": "London", "time": "2025-05-01 08:00", "airline": "Global Airways"}
87-
]
88-
response = generate_response(query, flight_data)
89-
self.assertIn("NY100", response)
90-
self.assertIn("New York", response)
91-
self.assertIn("London", response)
85+
@patch("ollama_api.check_ollama_availability")
86+
def test_generate_response_ollama_unavailable(mock_check, mock_env):
87+
mock_check.return_value = (False, "Server unavailable")
88+
flights = [{"flight_number": "NY100", "origin": "New York", "destination": "London"}]
89+
result = generate_response("flights from New York", flights)
90+
assert "Flight NY100" in result, "Should use fallback when Ollama unavailable"
91+
assert "New York to London" in result, "Should include route in fallback"
9292

93-
if __name__ == "__main__":
94-
unittest.main()
93+
@patch("ollama_api.check_ollama_availability")
94+
def test_generate_response_ollama_not_initialized(mock_check, mock_env):
95+
mock_check.return_value = (True, "Server available")
96+
with patch("ollama_api.ollama_llm", None):
97+
flights = [{"flight_number": "NY100"}]
98+
result = generate_response("test query", flights)
99+
assert "Flight NY100" in result, "Should use fallback when ollama_llm is None"
100+
101+
@patch("ollama_api.check_ollama_availability")
102+
@patch("ollama_api.ollama_llm.invoke")
103+
def test_generate_response_ollama_failure(mock_invoke, mock_check, mock_env):
104+
mock_check.return_value = (True, "Server available")
105+
mock_invoke.side_effect = Exception("LLM error")
106+
with patch("ollama_api.ollama_llm", Mock()):
107+
flights = [{"flight_number": "NY100"}]
108+
result = generate_response("test query", flights)
109+
assert "Flight NY100" in result, "Should use fallback on Ollama exception"

0 commit comments

Comments
 (0)