diff --git a/.gitignore b/.gitignore index 509495d..ae5f8b0 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,7 @@ bower_components/ # Test output test-output/ *.tap + +# Karate reports +tests/target/ +tests/results/ diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..bd4d3cf --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,14 @@ +FROM openjdk:17 + +WORKDIR /app + +# Copy test files +COPY tests /app/tests + +# Make sure karate-config.js is in the right place +RUN mkdir -p /app/tests/karate/src/test/resources +# Create directory for test results +RUN mkdir -p /app/target/karate-reports + +# Set default command +CMD ["java", "-jar", "/app/tests/karate.jar", "--configdir", "/app/tests/karate/src/test/resources", "/app/tests/karate/features"] \ No newline at end of file diff --git a/README.md b/README.md index 60df92e..edbfa75 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Ensures funds are automatically released based on the terms of the agreement, wi 0. Install Docker and Docker Compose 1. Download the Hasura global binary. See steps [here](https://hasura.io/docs/2.0/hasura-cli/install-hasura-cli/) -2. Run +2. Run ```shell @@ -84,28 +84,105 @@ hasura migrate apply --admin-secret myadminsecretkey If you wanna use the hasura web console and access it on `http://localhost:9695/`: - ```shell hasura console --admin-secret myadminsecretkey ``` And you should be good to go to start and work on this. - ## πŸ“‹ **Known Issues** -### πŸ“ **Title** +### πŸ“ **Title** + **Error Running Docker Compose** -### ❌ **Error Message** +### ❌ **Error Message** + > `Rosetta error: Rosetta is only intended to run on Apple Silicon with a macOS host using Virtualization.framework with Rosetta mode enabled` -### πŸ” **Error Description** -1. Run `docker compose up -d`. -2. If the **Backend postgres-1 module** can't start and shows the error above: - - This is due to an issue with Rosetta settings on Apple Silicon devices. -3. βœ… **Solution:** +### πŸ” **Error Description** + +1. Run `docker compose up -d`. +2. If the **Backend postgres-1 module** can't start and shows the error above: + - This is due to an issue with Rosetta settings on Apple Silicon devices. +3. βœ… **Solution:** - Go to **Docker Settings** and disable the: - `Use Rosetta for x86_64/amd64 emulation on Apple Silicon` selection button. - - πŸ”„ Restart Docker. - - πŸš€ It should now run great! \ No newline at end of file + `Use Rosetta for x86_64/amd64 emulation on Apple Silicon` selection button. + - πŸ”„ Restart Docker. + - πŸš€ It should now run great! + +# Backend Tests + +This project uses Karate framework for API testing. The tests are designed to run in a Docker environment. + +## Prerequisites + +- Docker +- Docker Compose + +## Project Structure + +``` +backend/ +β”œβ”€β”€ docker-compose-test.yml +β”œβ”€β”€ Dockerfile.test +└── tests/ + β”œβ”€β”€ karate.jar + └── karate/ + β”œβ”€β”€ features/ + β”‚ β”œβ”€β”€ auth/ + β”‚ β”‚ β”œβ”€β”€ login.feature + β”‚ β”‚ └── permissions.feature + β”‚ └── users/ + β”‚ β”œβ”€β”€ create.feature + β”‚ └── query.feature + └── src/ + └── test/ + └── resources/ + └── karate-config.js +``` + +## Running Tests + +To run all tests: + +```bash +docker compose -f docker-compose-test.yml up --build --abort-on-container-exit +``` + +This command will: + +1. Build the test container +2. Start PostgreSQL and Hasura containers +3. Run all Karate tests +4. Show test results in the console +5. Generate HTML reports in `target/karate-reports/` + +## Test Reports + +After running the tests, you can find the HTML reports at: + +- Summary: `tests/results/karate-summary.html` +- Detailed: `tests/results/karate-tags.html` + +## Development + +### Adding New Tests + +1. Create new `.feature` files in `tests/karate/features/` +2. Follow the Karate DSL syntax +3. Tests will be automatically picked up when running the test command + +### Configuration + +- Main config: `tests/karate/src/test/resources/karate-config.js` +- Database config: `docker-compose-test.yml` +- Test environment: `Dockerfile.test` + +## Troubleshooting + +If tests fail with connection errors: + +1. Ensure all containers are running: `docker compose -f docker-compose-test.yml ps` +2. Check container logs: `docker compose -f docker-compose-test.yml logs` +3. Verify network connectivity: `docker network inspect backend_test-network` diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..922ac38 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,53 @@ +services: + postgres_test: + image: postgres:15 + restart: always + environment: + POSTGRES_PASSWORD: postgrespassword + ports: + - "5432:5432" + networks: + - test-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + graphql-engine-test: + image: hasura/graphql-engine:latest + ports: + - "8081:8080" + depends_on: + postgres_test: + condition: service_healthy + environment: + HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres_test:5432/postgres + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" + HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey + HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256", "key": "12345678901234567890123456789012", "claims_format": "json", "claims_namespace": "https://hasura.io/jwt/claims"}' + HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "anonymous" + HASURA_GRAPHQL_LOG_LEVEL: "debug" + HASURA_GRAPHQL_ENABLED_LOG_TYPES: "startup,http-log,webhook-log,websocket-log,query-log" + ACTION_BASE_URL: http://action-handler:3000 + volumes: + - ./tests/metadata:/hasura-metadata + networks: + - test-network + + karate: + build: + context: . + dockerfile: Dockerfile.test + volumes: + - ./tests/results:/app/target/karate-reports + depends_on: + graphql-engine-test: + condition: service_started + networks: + - test-network + +networks: + test-network: + driver: bridge diff --git a/package.json b/package.json index b6c4145..20eeee0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "**SafeTrust** is a decentralized platform designed to revolutionize P2P transactions, providing secure deposits and payments powered by blockchain and trustless technologies. 🌐✨ Experience transparency and reliability in every cryptocurrency transaction. πŸ’ΈπŸ”’", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "java -jar /app/tests/karate.jar /app/tests/karate/features" }, "repository": { "type": "git", @@ -23,6 +23,7 @@ "express-rate-limit": "^7.4.1", "graphql-request": "^7.1.2", "nodemailer": "^6.9.16", - "winston": "^3.17.0" + "winston": "^3.17.0", + "cucumber-html-reporter": "^5.5.0" } } diff --git a/tests/karate.jar b/tests/karate.jar new file mode 100644 index 0000000..f33e300 Binary files /dev/null and b/tests/karate.jar differ diff --git a/tests/karate/features/auth/login.feature b/tests/karate/features/auth/login.feature new file mode 100644 index 0000000..8010b01 --- /dev/null +++ b/tests/karate/features/auth/login.feature @@ -0,0 +1,28 @@ +Feature: User Authentication + +Background: + * print '=== Loading feature file ===' + * url baseUrl + * print 'URL set to:', baseUrl + * header x-hasura-admin-secret = adminSecret + +Scenario: Check test database connection + * print 'Starting database check' + Given path '/' + And def query = + """ + { + "query": "query { __type(name: \"User\") { fields { name type { name kind } } } }" + } + """ + And request query + When method POST + Then status 200 + * print 'Schema:', response.data + +Scenario: Check test environment health + * print 'Starting health check' + Given url baseUrl.replace('/v1/graphql', '/healthz') + When method GET + Then status 200 + * print 'Health response:', response \ No newline at end of file diff --git a/tests/karate/features/auth/permissions.feature b/tests/karate/features/auth/permissions.feature new file mode 100644 index 0000000..3b6a9b3 --- /dev/null +++ b/tests/karate/features/auth/permissions.feature @@ -0,0 +1,16 @@ +Feature: Authorization Permissions + +Background: + * url baseUrl + * print '=== Loading feature file ===' + * print 'URL set to:', baseUrl + +Scenario: User can access their own data + * def token = tokenHelper({ uid: 'test-user', role: 'user' }) + * header Authorization = token + * header x-hasura-admin-secret = adminSecret + + Given path '/' + And request { query: "{ __type(name: \"User\") { fields { name } } }" } + When method POST + Then status 200 diff --git a/tests/karate/features/users/create.feature b/tests/karate/features/users/create.feature new file mode 100644 index 0000000..2ad2c67 --- /dev/null +++ b/tests/karate/features/users/create.feature @@ -0,0 +1,14 @@ +Feature: Create User + +Background: + * url baseUrl + * print '=== Loading feature file ===' + * print 'URL set to:', baseUrl + +Scenario: Check Hasura health + Given path '' + And header x-hasura-admin-secret = adminSecret + And request { query: "query { __typename }" } + When method POST + Then status 200 + And match response == { data: { __typename: 'query_root' } } diff --git a/tests/karate/features/users/query.feature b/tests/karate/features/users/query.feature new file mode 100644 index 0000000..86c8291 --- /dev/null +++ b/tests/karate/features/users/query.feature @@ -0,0 +1,19 @@ +Feature: Query Users + +Background: + * url baseUrl + * print '=== Loading feature file ===' + * print 'URL set to:', baseUrl + * header x-hasura-admin-secret = adminSecret + +Scenario: Query existing user + Given request { query: "{ __typename }" } + When method POST + Then status 200 + And match response == { data: { __typename: 'query_root' } } + +Scenario: Query non-existing user + Given request { query: "{ __typename }" } + When method POST + Then status 200 + And match response == { data: { __typename: 'query_root' } } \ No newline at end of file diff --git a/tests/karate/features/users/wallets.feature b/tests/karate/features/users/wallets.feature new file mode 100644 index 0000000..761f553 --- /dev/null +++ b/tests/karate/features/users/wallets.feature @@ -0,0 +1,31 @@ +Feature: User Wallets + +Background: + * url baseUrl + * print '=== Loading feature file ===' + * print 'URL set to:', baseUrl + +Scenario: Get schema information + Given header Authorization = tokenHelper({ role: 'admin' }) + And header x-hasura-admin-secret = adminSecret + And request + """ + { + "query": " + query IntrospectionQuery { + __schema { + queryType { + fields { + name + description + } + } + } + } + " + } + """ + When method POST + Then status 200 + And match response.errors == '#notpresent' + * print 'Available queries:', response.data.__schema.queryType.fields \ No newline at end of file diff --git a/tests/karate/src/test/resources/karate-config.js b/tests/karate/src/test/resources/karate-config.js new file mode 100644 index 0000000..304facf --- /dev/null +++ b/tests/karate/src/test/resources/karate-config.js @@ -0,0 +1,50 @@ +function fn() { + karate.log("=== Starting karate-config.js ==="); + + // Configure pretty logging + karate.configure("logPrettyRequest", true); + karate.configure("logPrettyResponse", true); + + // Basic configuration + var config = { + baseUrl: "http://graphql-engine-test:8080/v1/graphql", + adminSecret: "myadminsecretkey", + }; + + // Token helper function + config.tokenHelper = function (claims) { + var Base64 = Java.type("java.util.Base64"); + var defaultClaims = { + "https://hasura.io/jwt/claims": { + "x-hasura-allowed-roles": ["user"], + "x-hasura-default-role": "user", + "x-hasura-user-id": "00000000-0000-0000-0000-000000000000", + }, + }; + + if (claims && claims.role === "admin") { + defaultClaims["https://hasura.io/jwt/claims"] = { + "x-hasura-allowed-roles": ["user", "admin"], + "x-hasura-default-role": "admin", + "x-hasura-user-id": "admin-user", + }; + } + + var header = { alg: "HS256", typ: "JWT" }; + + // Properly encode each part + var headerBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(JSON.stringify(header).getBytes("UTF-8")); + var payloadBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(JSON.stringify(defaultClaims).getBytes("UTF-8")); + + return "Bearer " + headerBase64 + "." + payloadBase64 + ".your-secret-key"; + }; + + // Set default headers + config.headers = { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": config.adminSecret, + }; + + karate.log("Config initialized:", config); + return config; +}