diff --git a/README.md b/README.md index 9e475d0..55de833 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ This repo contains the samples for [Keploy's](https://keploy.io) integration wit 4. [FastAPI-Twilio](https://github.com/keploy/samples-python/tree/main/fastapi-twilio) - This application is a SMS sending API built using Python's FastAPI and Twilio for their SMS sharing service. +5. [Flask-Redis](https://github.com/keploy/samples-python/tree/main/flask-redis) - This Flask-based application provides a book management system utilizing Redis for caching and storage. It supports adding, retrieving, updating, and deleting book records, with optimized search functionality and cache management for improved performance. The API endpoints ensure efficient data handling and quick access to book information. + ## Community Support ❤️ ### 🤔 Questions? diff --git a/flask-redis/Dockerfile b/flask-redis/Dockerfile new file mode 100644 index 0000000..9c0e897 --- /dev/null +++ b/flask-redis/Dockerfile @@ -0,0 +1,26 @@ +# Use the official Python image from the Docker Hub +FROM python:3.10-slim + +# RUN apt-get update && apt-get install -y curl +# Set the working directory in the container +WORKDIR /app + +# Copy the requirements file into the container +COPY requirements.txt . + +# Install the Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application code into the container +COPY . . + +# Download Keploy CA certificate and setup script +# RUN curl -o ca.crt https://raw.githubusercontent.com/keploy/keploy/main/pkg/core/proxy/asset/ca.crt && \ +# curl -o setup_ca.sh https://raw.githubusercontent.com/keploy/keploy/main/pkg/core/proxy/asset/setup_ca.sh && \ +# chmod +x setup_ca.sh # Make the setup script executable + +# Expose the port the app runs on +EXPOSE 5000 + +# Define the command to run the app +CMD ["python", "app.py"] diff --git a/flask-redis/README.md b/flask-redis/README.md new file mode 100644 index 0000000..7364e9d --- /dev/null +++ b/flask-redis/README.md @@ -0,0 +1,160 @@ +# flask-redis + +A sample App using flask and redis + +## Setup application + +1. Clone the repository and move to flask-redis folder +2. Create a .env file and copy-paste below credentials: + +```bash +REDIS_HOST=redis +REDIS_PORT=6379 +``` + +# Installing Redis + +```sh +brew install redis +``` +If homebrew is not installed, then go to https://brew.sh/ and install it. + +```bash +git clone https://github.com/keploy/samples-typescript && cd samples-typescript/flask-redis + +# Install the dependencies +pip3 install -r requirements.txt +``` + +# Installing Keploy + +Let's get started by setting up the Keploy alias with this command: + +```sh +curl -O https://raw.githubusercontent.com/keploy/keploy/main/keploy.sh && source keploy.sh +``` + +## Using Keploy : + +There are 2 ways you can run this sample application. + +1. [Natively on Linux/WSL](#natively-on-ubuntuwsl) +2. [Using Docker](#running-sample-app-using-docker) + +# Natively on Ubuntu/WSL + +## Let's install certificates + +1. **Install required packages:** + + ```sh + sudo apt-get install -y --no-install-recommends ca-certificates curl + ``` + + This command installs necessary packages without additional recommended packages. + +2. **Download CA certificate:** + + ```sh + curl -o ca.crt https://raw.githubusercontent.com/keploy/keploy/main/pkg/core/proxy/asset/ca.crt + ``` + + This command downloads the CA certificate to `ca.crt`. + +3. **Download setup script:** + + ```sh + curl -o setup_ca.sh https://raw.githubusercontent.com/keploy/keploy/main/pkg/core/proxy/asset/setup_ca.sh + ``` + + This command downloads the setup script to `setup_ca.sh`. + +4. **Make the setup script executable:** + + ```sh + chmod +x setup_ca.sh + ``` + + This command changes the permissions of `setup_ca.sh` to make it executable. + +5. **Run the setup script:** + + ```sh + source ./setup_ca.sh + ``` + + This command executes the setup script in the current shell. + +6. **Start the redis server:** + ```sh + redis-server + ``` + This command starts the redis server. + +## Capture the test cases + +1. **Start recording tests:** + ```bash + sudo -E env "PATH=$PATH" keploybin record -c 'python3 app.py' + ``` + +## Let's Generate the test cases + +Make API Calls using Hoppscotch, Postman or cURL command. Keploy will capture those calls to generate test suites containing test cases and data mocks. + +1. Refer to flask-redis/api.txt to make api calls. + +2. **Observe terminal output:** + Let's go ahead and create a few more test cases for different endpoints! + +## Running the test cases + +1. **Start the application:** + + ```bash + python3 app.py + ``` + +2. **Run the recorded tests:** + + ```bash + sudo -E env "PATH=$PATH" keploybin test -c 'python3 app.py' --delay 10 + ``` + +3. **Observe test run results:** + _Voila!! Our test cases have passed 🌟_ + +--- + +# Using Docker + +Since we have to setup our app using docker(make sure your docker is running) + +## Create a custom network for Keploy since we are using the Docker + +```bash +docker network create keploy-network +``` + +## Capture the testcases + +We will run the keploy in record mode with docker-compose to start our application:- + +```bash +keploy record -c "sudo docker-compose up" --containerName "flask-web" + +``` + +#### Let's generate the testcases. + +Make API Calls using [Hoppscotch](https://hoppscotch.io), [Postman](https://postman.com) or curl command. Keploy with capture those calls to generate the test-suites containing testcases and data mocks. + +1. Refer to flask-redis/api.txt to make api calls + +## Running the testcases + +```bash +keploy test -c 'sudo docker-compose up' --containerName "flask-web" --delay 10 +``` + +_Voila!! Our testcases has passed 🌟_ diff --git a/flask-redis/api.txt b/flask-redis/api.txt new file mode 100644 index 0000000..68b4b23 --- /dev/null +++ b/flask-redis/api.txt @@ -0,0 +1,44 @@ +--------------------------------------------------------------------------------------------------- +// Add a Book + +curl -X POST http://localhost:5000/books/ \ +-H "Content-Type: application/json" \ +-d '{"title": "1984", "author": "George Orwell"}' + +--------------------------------------------------------------------------------------------------- + +// Get All Books(with Pagination) + +curl -X GET "http://localhost:5000/books/?page=1&limit=10" + +--------------------------------------------------------------------------------------------------- + +// Get Book Details + +curl -X GET http://localhost:5000/books/1 + +--------------------------------------------------------------------------------------------------- + + +//Search Books by Title + +curl -X GET "http://localhost:5000/books/search?query=1984" + + +--------------------------------------------------------------------------------------------------- + +//Update a Book + +curl -X PUT http://localhost:5000/books/1 \ +-H "Content-Type: application/json" \ +-d '{"title": "1984 - Updated", "author": "George Orwell"}' + + +--------------------------------------------------------------------------------------------------- + +//Delete a Book + +curl -X DELETE http://localhost:5000/books/1 + +--------------------------------------------------------------------------------------------------- + diff --git a/flask-redis/app.py b/flask-redis/app.py new file mode 100644 index 0000000..bf701d1 --- /dev/null +++ b/flask-redis/app.py @@ -0,0 +1,14 @@ +from flask import Flask +from routes.book_routes import book + +app = Flask(__name__) + +# Register Blueprints +app.register_blueprint(book, url_prefix="/books") + +@app.route('/', methods=['GET']) +def hello(): + return {"message": "Welcome to the Book Management System!"}, 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/flask-redis/docker-compose.yml b/flask-redis/docker-compose.yml new file mode 100644 index 0000000..99a6016 --- /dev/null +++ b/flask-redis/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3.8" + +services: + web: + build: . + container_name: flask-web + restart: always + ports: + - "5000:5000" + depends_on: + - redis + environment: + - FLASK_ENV=development + - REDIS_HOST=redis + - REDIS_PORT=6379 + # volumes: + # - .:/usr/src/app + networks: + - keploy-network + + redis: + image: "redis:alpine" + container_name: redis-server + restart: always + ports: + - "6379:6379" + # volumes: + # - db_data:/var/lib/redis + networks: + - keploy-network + +networks: + keploy-network: + external: true +# volumes: +# db_data: diff --git a/flask-redis/dump.rdb b/flask-redis/dump.rdb new file mode 100644 index 0000000..601ee4a Binary files /dev/null and b/flask-redis/dump.rdb differ diff --git a/flask-redis/keploy.yml b/flask-redis/keploy.yml new file mode 100755 index 0000000..7b17386 --- /dev/null +++ b/flask-redis/keploy.yml @@ -0,0 +1,43 @@ +path: "" +appId: 0 +appName: "" +command: sudo docker-compose up +port: 0 +dnsPort: 26789 +proxyPort: 16789 +debug: false +disableTele: false +disableANSI: false +containerName: flask-web +networkName: "" +buildDelay: 30 +test: + selectedTests: {} + globalNoise: + global: {} + test-sets: {} + delay: 5 + apiTimeout: 5 + skipCoverage: false + coverageReportPath: "" + ignoreOrdering: true + mongoPassword: default@123 + language: "" + removeUnusedMocks: false + fallBackOnMiss: false + jacocoAgentPath: "" + basePath: "" + mocking: true + ignoredTests: {} +record: + filters: [] + recordTimer: 0s +configPath: "" +bypassRules: [] +generateGithubActions: false +keployContainer: keploy-v2 +keployNetwork: keploy-network +cmdType: native +inCi: false + +# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. diff --git a/flask-redis/keploy/reports/test-run-0/test-set-0-report.yaml b/flask-redis/keploy/reports/test-run-0/test-set-0-report.yaml new file mode 100755 index 0000000..34f4af7 --- /dev/null +++ b/flask-redis/keploy/reports/test-run-0/test-set-0-report.yaml @@ -0,0 +1,606 @@ +version: api.keploy.io/v1beta1 +name: test-set-0-report +status: PASSED +success: 6 +failure: 0 +ignored: 0 +total: 6 +tests: + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-1 + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/ + header: + Accept: '*/*' + Content-Length: "44" + Content-Type: application/json + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: '{"title": "1984", "author": "George Orwell"}' + timestamp: 2024-08-02T22:15:10.596402605Z + resp: + status_code: 201 + header: + Content-Length: "59" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "book_id": 1, + "message": "Book added successfully" + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 201 + actual: 201 + headers_result: + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + - normal: true + expected: + key: Content-Length + value: + - "59" + actual: + key: Content-Length + value: + - "59" + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:10 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + body_result: + - normal: true + type: JSON + expected: | + { + "book_id": 1, + "message": "Book added successfully" + } + actual: | + { + "book_id": 1, + "message": "Book added successfully" + } + dep_result: [] + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-2 + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/?page=1&limit=10 + url_params: + limit: "10" + page: "1" + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:17.339856691Z + resp: + status_code: 200 + header: + Content-Length: "88" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "books": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 200 + actual: 200 + headers_result: + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:17 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + - normal: true + expected: + key: Content-Length + value: + - "88" + actual: + key: Content-Length + value: + - "88" + body_result: + - normal: true + type: JSON + expected: | + { + "books": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + actual: | + { + "books": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + dep_result: [] + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-3 + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/1 + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:24.919087736Z + resp: + status_code: 200 + header: + Content-Length: "51" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "author": "George Orwell", + "title": "1984" + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 200 + actual: 200 + headers_result: + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:24 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + - normal: true + expected: + key: Content-Length + value: + - "51" + actual: + key: Content-Length + value: + - "51" + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + body_result: + - normal: true + type: JSON + expected: | + { + "author": "George Orwell", + "title": "1984" + } + actual: | + { + "author": "George Orwell", + "title": "1984" + } + dep_result: [] + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-4 + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/search?query=1984 + url_params: + query: "1984" + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:32.960447823Z + resp: + status_code: 200 + header: + Content-Length: "90" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "results": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 200 + actual: 200 + headers_result: + - normal: true + expected: + key: Content-Length + value: + - "90" + actual: + key: Content-Length + value: + - "90" + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:32 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + body_result: + - normal: true + type: JSON + expected: | + { + "results": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + actual: | + { + "results": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + dep_result: [] + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-5 + req: + method: PUT + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/1 + header: + Accept: '*/*' + Content-Length: "54" + Content-Type: application/json + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: '{"title": "1984 - Updated", "author": "George Orwell"}' + timestamp: 2024-08-02T22:15:39.802018618Z + resp: + status_code: 200 + header: + Content-Length: "123" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "book": { + "author": "George Orwell", + "title": "1984 - Updated" + }, + "message": "Book updated successfully" + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 200 + actual: 200 + headers_result: + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:39 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + - normal: true + expected: + key: Content-Length + value: + - "123" + actual: + key: Content-Length + value: + - "123" + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + body_result: + - normal: true + type: JSON + expected: | + { + "book": { + "author": "George Orwell", + "title": "1984 - Updated" + }, + "message": "Book updated successfully" + } + actual: | + { + "book": { + "author": "George Orwell", + "title": "1984 - Updated" + }, + "message": "Book updated successfully" + } + dep_result: [] + - kind: Http + name: test-set-0 + status: PASSED + started: 1722636974 + completed: 1722636974 + test_case_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0 + mock_path: /Users/amanrai/Desktop/flask-redis/keploy/test-set-0/mocks + test_case_id: test-6 + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://172.18.0.4:5000/books/1 + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:47.228393997Z + resp: + status_code: 200 + header: + Content-Length: "45" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:16:14 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "message": "Book deleted successfully" + } + status_message: "" + proto_major: 0 + proto_minor: 0 + timestamp: 0001-01-01T00:00:00Z + noise: + header.Date: [] + result: + status_code: + normal: true + expected: 200 + actual: 200 + headers_result: + - normal: true + expected: + key: Content-Length + value: + - "45" + actual: + key: Content-Length + value: + - "45" + - normal: true + expected: + key: Content-Type + value: + - application/json + actual: + key: Content-Type + value: + - application/json + - normal: true + expected: + key: Date + value: + - Fri, 02 Aug 2024 22:15:47 GMT + actual: + key: Date + value: + - Fri, 02 Aug 2024 22:16:14 GMT + - normal: true + expected: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + actual: + key: Server + value: + - Werkzeug/3.0.3 Python/3.10.14 + body_result: + - normal: true + type: JSON + expected: | + { + "message": "Book deleted successfully" + } + actual: | + { + "message": "Book deleted successfully" + } + dep_result: [] +test_set: test-set-0 diff --git a/flask-redis/keploy/test-set-0/mocks.yaml b/flask-redis/keploy/test-set-0/mocks.yaml new file mode 100755 index 0000000..1e3ba8b --- /dev/null +++ b/flask-redis/keploy/test-set-0/mocks.yaml @@ -0,0 +1,417 @@ +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-0 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$6\r\nCLIENT\r\n$7\r\nSETINFO\r\n$8\r\nLIB-NAME\r\n$8\r\nredis-py\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "+OK\r\n" + reqtimestampmock: 2024-08-02T22:15:10.6084523Z + restimestampmock: 2024-08-02T22:15:10.608930466Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-1 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$6\r\nCLIENT\r\n$7\r\nSETINFO\r\n$7\r\nLIB-VER\r\n$5\r\n5.0.8\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "+OK\r\n" + reqtimestampmock: 2024-08-02T22:15:10.610066633Z + restimestampmock: 2024-08-02T22:15:10.610752633Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-2 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*3\r\n$6\r\nINCRBY\r\n$7\r\nbook_id\r\n$1\r\n1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":1\r\n" + reqtimestampmock: 2024-08-02T22:15:10.611978716Z + restimestampmock: 2024-08-02T22:15:10.612786841Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-3 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*6\r\n$4\r\nHSET\r\n$6\r\nbook:1\r\n$5\r\ntitle\r\n$4\r\n1984\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":2\r\n" + reqtimestampmock: 2024-08-02T22:15:10.613938591Z + restimestampmock: 2024-08-02T22:15:10.614915925Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-4 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$3\r\nGET\r\n$18\r\nbooks_cache:page_1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "$-1\r\n" + reqtimestampmock: 2024-08-02T22:15:17.343482553Z + restimestampmock: 2024-08-02T22:15:17.344991261Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-5 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$4\r\nKEYS\r\n$6\r\nbook:*\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "*1\r\n$6\r\nbook:1\r\n" + reqtimestampmock: 2024-08-02T22:15:17.345461178Z + restimestampmock: 2024-08-02T22:15:17.346119386Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-6 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$7\r\nHGETALL\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "*4\r\n$5\r\ntitle\r\n$4\r\n1984\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + reqtimestampmock: 2024-08-02T22:15:17.346484636Z + restimestampmock: 2024-08-02T22:15:17.346881469Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-7 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$5\r\nSETEX\r\n$18\r\nbooks_cache:page_1\r\n$3\r\n300\r\n$46\r\n[{\"title\": \"1984\", \"author\": \"George Orwell\"}]\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "+OK\r\n" + reqtimestampmock: 2024-08-02T22:15:17.347539428Z + restimestampmock: 2024-08-02T22:15:17.348107886Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-8 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$7\r\nHGETALL\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "*4\r\n$5\r\ntitle\r\n$4\r\n1984\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + reqtimestampmock: 2024-08-02T22:15:24.91868625Z + restimestampmock: 2024-08-02T22:15:24.918989625Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-9 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$3\r\nGET\r\n$17\r\nsearch:books:1984\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "$-1\r\n" + reqtimestampmock: 2024-08-02T22:15:32.959388379Z + restimestampmock: 2024-08-02T22:15:32.959604088Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-10 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$4\r\nKEYS\r\n$6\r\nbook:*\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "*1\r\n$6\r\nbook:1\r\n" + reqtimestampmock: 2024-08-02T22:15:32.959842713Z + restimestampmock: 2024-08-02T22:15:32.959998921Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-11 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$7\r\nHGETALL\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "*4\r\n$5\r\ntitle\r\n$4\r\n1984\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + reqtimestampmock: 2024-08-02T22:15:32.960231671Z + restimestampmock: 2024-08-02T22:15:32.960389213Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-12 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$5\r\nSETEX\r\n$17\r\nsearch:books:1984\r\n$3\r\n300\r\n$46\r\n[{\"title\": \"1984\", \"author\": \"George Orwell\"}]\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "+OK\r\n" + reqtimestampmock: 2024-08-02T22:15:32.960706004Z + restimestampmock: 2024-08-02T22:15:32.960838838Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-13 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$6\r\nEXISTS\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":1\r\n" + reqtimestampmock: 2024-08-02T22:15:39.801969841Z + restimestampmock: 2024-08-02T22:15:39.802299174Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-14 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$4\r\nHSET\r\n$6\r\nbook:1\r\n$5\r\ntitle\r\n$14\r\n1984 - Updated\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":0\r\n" + reqtimestampmock: 2024-08-02T22:15:39.802669008Z + restimestampmock: 2024-08-02T22:15:39.803126174Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-15 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*4\r\n$4\r\nHSET\r\n$6\r\nbook:1\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":0\r\n" + reqtimestampmock: 2024-08-02T22:15:39.803492508Z + restimestampmock: 2024-08-02T22:15:39.803768216Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-16 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*3\r\n$4\r\nHGET\r\n$6\r\nbook:1\r\n$5\r\ntitle\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "$14\r\n1984 - Updated\r\n" + reqtimestampmock: 2024-08-02T22:15:39.804229258Z + restimestampmock: 2024-08-02T22:15:39.804523466Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-17 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*3\r\n$4\r\nHGET\r\n$6\r\nbook:1\r\n$6\r\nauthor\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "$13\r\nGeorge Orwell\r\n" + reqtimestampmock: 2024-08-02T22:15:39.805022091Z + restimestampmock: 2024-08-02T22:15:39.805174924Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-18 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*6\r\n$5\r\nHMSET\r\n$6\r\nbook:1\r\n$5\r\ntitle\r\n$14\r\n1984 - Updated\r\n$6\r\nauthor\r\n$13\r\nGeorge Orwell\r\n" + redisresponses: + - origin: server + message: + - type: string + data: "+OK\r\n" + reqtimestampmock: 2024-08-02T22:15:39.806458299Z + restimestampmock: 2024-08-02T22:15:39.807372758Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-19 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$6\r\nEXISTS\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":1\r\n" + reqtimestampmock: 2024-08-02T22:15:47.227235428Z + restimestampmock: 2024-08-02T22:15:47.227446803Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-20 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$3\r\nDEL\r\n$6\r\nbook:1\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":1\r\n" + reqtimestampmock: 2024-08-02T22:15:47.227812303Z + restimestampmock: 2024-08-02T22:15:47.228129511Z +--- +version: api.keploy.io/v1beta1 +kind: Redis +name: mock-21 +spec: + metadata: + type: config + redisrequests: + - origin: client + message: + - type: string + data: "*2\r\n$3\r\nDEL\r\n$18\r\nbooks_cache:page_*\r\n" + redisresponses: + - origin: server + message: + - type: string + data: ":0\r\n" + reqtimestampmock: 2024-08-02T22:15:47.228357053Z + restimestampmock: 2024-08-02T22:15:47.228491803Z diff --git a/flask-redis/keploy/test-set-0/tests/test-1.yaml b/flask-redis/keploy/test-set-0/tests/test-1.yaml new file mode 100755 index 0000000..ddc1544 --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-1.yaml @@ -0,0 +1,47 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-1 +spec: + metadata: {} + req: + method: POST + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/ + header: + Accept: '*/*' + Content-Length: "44" + Content-Type: application/json + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: '{"title": "1984", "author": "George Orwell"}' + timestamp: 2024-08-02T22:15:10.596402605Z + resp: + status_code: 201 + header: + Content-Length: "59" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:10 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "book_id": 1, + "message": "Book added successfully" + } + status_message: Created + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:12.681157884Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636912 +curl: |- + curl --request POST \ + --url http://localhost:5000/books/ \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ + --data '{"title": "1984", "author": "George Orwell"}' diff --git a/flask-redis/keploy/test-set-0/tests/test-2.yaml b/flask-redis/keploy/test-set-0/tests/test-2.yaml new file mode 100755 index 0000000..85115b2 --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-2.yaml @@ -0,0 +1,50 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-2 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/?page=1&limit=10 + url_params: + limit: "10" + page: "1" + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:17.339856691Z + resp: + status_code: 200 + header: + Content-Length: "88" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:17 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "books": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:19.428364137Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636919 +curl: | + curl --request GET \ + --url http://localhost:5000/books/?page=1&limit=10 \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ + --header 'Accept: */*' \ diff --git a/flask-redis/keploy/test-set-0/tests/test-3.yaml b/flask-redis/keploy/test-set-0/tests/test-3.yaml new file mode 100755 index 0000000..316a90d --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-3.yaml @@ -0,0 +1,43 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-3 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/1 + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:24.919087736Z + resp: + status_code: 200 + header: + Content-Length: "51" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:24 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "author": "George Orwell", + "title": "1984" + } + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:26.965315001Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636926 +curl: | + curl --request GET \ + --url http://localhost:5000/books/1 \ + --header 'Accept: */*' \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ diff --git a/flask-redis/keploy/test-set-0/tests/test-4.yaml b/flask-redis/keploy/test-set-0/tests/test-4.yaml new file mode 100755 index 0000000..e659f2f --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-4.yaml @@ -0,0 +1,49 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-4 +spec: + metadata: {} + req: + method: GET + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/search?query=1984 + url_params: + query: "1984" + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:32.960447823Z + resp: + status_code: 200 + header: + Content-Length: "90" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:32 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "results": [ + { + "author": "George Orwell", + "title": "1984" + } + ] + } + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:34.98447863Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636934 +curl: | + curl --request GET \ + --url http://localhost:5000/books/search?query=1984 \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ + --header 'Accept: */*' \ diff --git a/flask-redis/keploy/test-set-0/tests/test-5.yaml b/flask-redis/keploy/test-set-0/tests/test-5.yaml new file mode 100755 index 0000000..7f38a10 --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-5.yaml @@ -0,0 +1,50 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-5 +spec: + metadata: {} + req: + method: PUT + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/1 + header: + Accept: '*/*' + Content-Length: "54" + Content-Type: application/json + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: '{"title": "1984 - Updated", "author": "George Orwell"}' + timestamp: 2024-08-02T22:15:39.802018618Z + resp: + status_code: 200 + header: + Content-Length: "123" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:39 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "book": { + "author": "George Orwell", + "title": "1984 - Updated" + }, + "message": "Book updated successfully" + } + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:41.813988842Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636941 +curl: |- + curl --request PUT \ + --url http://localhost:5000/books/1 \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data '{"title": "1984 - Updated", "author": "George Orwell"}' diff --git a/flask-redis/keploy/test-set-0/tests/test-6.yaml b/flask-redis/keploy/test-set-0/tests/test-6.yaml new file mode 100755 index 0000000..228ce8e --- /dev/null +++ b/flask-redis/keploy/test-set-0/tests/test-6.yaml @@ -0,0 +1,42 @@ +version: api.keploy.io/v1beta1 +kind: Http +name: test-6 +spec: + metadata: {} + req: + method: DELETE + proto_major: 1 + proto_minor: 1 + url: http://localhost:5000/books/1 + header: + Accept: '*/*' + Host: localhost:5000 + User-Agent: curl/8.6.0 + body: "" + timestamp: 2024-08-02T22:15:47.228393997Z + resp: + status_code: 200 + header: + Content-Length: "45" + Content-Type: application/json + Date: Fri, 02 Aug 2024 22:15:47 GMT + Server: Werkzeug/3.0.3 Python/3.10.14 + body: | + { + "message": "Book deleted successfully" + } + status_message: OK + proto_major: 0 + proto_minor: 0 + timestamp: 2024-08-02T22:15:49.269456095Z + objects: [] + assertions: + noise: + header.Date: [] + created: 1722636949 +curl: | + curl --request DELETE \ + --url http://localhost:5000/books/1 \ + --header 'Accept: */*' \ + --header 'Host: localhost:5000' \ + --header 'User-Agent: curl/8.6.0' \ diff --git a/flask-redis/redisClient.py b/flask-redis/redisClient.py new file mode 100644 index 0000000..dc01e64 --- /dev/null +++ b/flask-redis/redisClient.py @@ -0,0 +1,12 @@ +import redis +import os + +# Connect to Redis server +client = redis.Redis( + host=os.getenv('REDIS_HOST', 'localhost'), + port=int(os.getenv('REDIS_PORT', 6379)), + db=0 +) + +def get_redis_client(): + return client diff --git a/flask-redis/requirements.txt b/flask-redis/requirements.txt new file mode 100644 index 0000000..bdb539d --- /dev/null +++ b/flask-redis/requirements.txt @@ -0,0 +1,3 @@ +Flask +redis +python-dotenv \ No newline at end of file diff --git a/flask-redis/routes/book_routes.py b/flask-redis/routes/book_routes.py new file mode 100644 index 0000000..6d58543 --- /dev/null +++ b/flask-redis/routes/book_routes.py @@ -0,0 +1,133 @@ +from flask import Blueprint, request, jsonify +from redisClient import get_redis_client +import json + +book = Blueprint('book', __name__) +redis_client = get_redis_client() + +@book.route('/', methods=['POST']) +def add_book(): + data = request.get_json() + title = data.get('title') + author = data.get('author') + + if not title or not author: + return jsonify({"error": "Title and author are required"}), 400 + + book_id = redis_client.incr('book_id') # Auto-increment ID for new book + redis_client.hset(f'book:{book_id}', mapping={"title": title, "author": author}) + + return jsonify({"message": "Book added successfully", "book_id": book_id}), 201 + + +@book.route('/', methods=['GET']) +def get_books(): + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 10)) + start = (page - 1) * limit + end = start + limit - 1 + + # Define your cache key here + cache_key = f'books_cache:page_{page}' + + # Check if the result is already in cache + cached_books = redis_client.get(cache_key) + if cached_books: + return jsonify(json.loads(cached_books)), 200 + + # If not cached, fetch from Redis + keys = redis_client.keys('book:*') + books = [ + {k.decode('utf-8'): v.decode('utf-8') for k, v in redis_client.hgetall(key).items()} + for key in keys + ] + redis_client.setex(cache_key, 300, json.dumps(books)) + + return jsonify({"books": books}), 200 + + +@book.route('/', methods=['GET']) +def get_book(book_id): + # Create a cache key based on the book ID + cache_key = f'book:{book_id}' + + # Check if the key exists in the cache + cached_book = redis_client.hgetall(cache_key) # Use hgetall for hash + + if cached_book: + # Decode the keys and values from bytes to strings + decoded_book = {key.decode('utf-8'): value.decode('utf-8') for key, value in cached_book.items()} + return jsonify(decoded_book), 200 + else: + return jsonify({"message": "Book not found"}), 404 + + +@book.route('/', methods=['PUT']) +def update_book(book_id): + data = request.get_json() + title = data.get('title') + author = data.get('author') + + if not redis_client.exists(f'book:{book_id}'): + return jsonify({"error": "Book not found"}), 404 + + if title: + redis_client.hset(f'book:{book_id}', "title", title) + if author: + redis_client.hset(f'book:{book_id}', "author", author) + + # Retrieve the updated book data + updated_book = { + "title": redis_client.hget(f'book:{book_id}', "title").decode('utf-8'), + "author": redis_client.hget(f'book:{book_id}', "author").decode('utf-8') + } + + # Update the cache with the latest data + redis_client.hmset(f'book:{book_id}', updated_book) + + return jsonify({"message": "Book updated successfully", "book": updated_book}), 200 + + + +@book.route('/', methods=['DELETE']) +def delete_book(book_id): + # Check if the book exists + if not redis_client.exists(f'book:{book_id}'): + return jsonify({"message": "Book not found"}), 404 + + # Delete the book + redis_client.delete(f'book:{book_id}') + + # Invalidate related caches + redis_client.delete(f'books_cache:page_*') # Adjust this if you cache other items + + return jsonify({"message": "Book deleted successfully"}), 200 + +@book.route('/search', methods=['GET']) +def search_books(): + query = request.args.get('query', '') + + if not query: + return jsonify({"error": "Search query is required"}), 400 + + cache_key = f'search:books:{query}' + cached_results = redis_client.get(cache_key) + + if cached_results: + return jsonify(json.loads(cached_results)), 200 + + all_books = redis_client.keys(f'book:*') + search_results = [] + + for book_key in all_books: + book_data = redis_client.hgetall(book_key) + title = book_data[b'title'].decode('utf-8') + author = book_data[b'author'].decode('utf-8') + if query.lower() in title.lower(): + search_results.append({"title": title, "author": author}) + + # Store search results in cache + redis_client.setex(cache_key, 300, json.dumps(search_results)) # Cache for 5 minutes + + return jsonify({"results": search_results}), 200 +