From 7fc5eda1f50912cb1de5aef7fcad1d635f80c527 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 16:34:39 +0000 Subject: [PATCH] Add JMeter test plan for Timesheet API with 17 transaction samplers - Test Plan: Timesheet API - Single User Validation - 17 HTTP Request samplers wrapped in Transaction Controllers - Covers auth, clients CRUD, work entries CRUD, reports (JSON/CSV/PDF), health check - JSON Extractors for dynamic CLIENT_ID and WORK_ENTRY_ID correlation - Response assertions for status codes and body content - Listeners: View Results Tree, Summary Report, Aggregate Report - README with usage instructions and configuration reference --- jmeter/README.md | 75 +++ jmeter/timesheet_api_test.jmx | 989 ++++++++++++++++++++++++++++++++++ 2 files changed, 1064 insertions(+) create mode 100644 jmeter/README.md create mode 100644 jmeter/timesheet_api_test.jmx diff --git a/jmeter/README.md b/jmeter/README.md new file mode 100644 index 00000000..2db61ca4 --- /dev/null +++ b/jmeter/README.md @@ -0,0 +1,75 @@ +# JMeter API Test Plan — Timesheet API + +## Overview + +This JMeter test plan (`timesheet_api_test.jmx`) validates the complete Timesheet API lifecycle with a single user session. It covers authentication, CRUD operations for clients and work entries, report generation (JSON, CSV, PDF), and the health check endpoint. + +## Test Scenarios (17 Transactions) + +| # | Transaction | Method | Endpoint | Key Assertions | +|---|------------|--------|----------|----------------| +| 01 | AUTH_Login | POST | `/api/auth/login` | 200/201, body contains "successful" | +| 02 | AUTH_GetCurrentUser | GET | `/api/auth/me` | 200, `user.email` matches | +| 03 | CLIENT_CreateClient | POST | `/api/clients` | 201, "Client created successfully" | +| 04 | CLIENT_GetAllClients | GET | `/api/clients` | 200, body contains "clients" | +| 05 | CLIENT_GetClientById | GET | `/api/clients/{id}` | 200, `client.id` matches | +| 06 | CLIENT_UpdateClient | PUT | `/api/clients/{id}` | 200, "Client updated successfully" | +| 07 | WORKENTRY_CreateWorkEntry | POST | `/api/work-entries` | 201, "Work entry created successfully" | +| 08 | WORKENTRY_GetAllWorkEntries | GET | `/api/work-entries` | 200, body contains "workEntries" | +| 09 | WORKENTRY_GetWorkEntriesByClient | GET | `/api/work-entries?clientId={id}` | 200 | +| 10 | WORKENTRY_GetWorkEntryById | GET | `/api/work-entries/{id}` | 200 | +| 11 | WORKENTRY_UpdateWorkEntry | PUT | `/api/work-entries/{id}` | 200, "Work entry updated successfully" | +| 12 | REPORT_GetClientReport | GET | `/api/reports/client/{id}` | 200, body contains "totalHours" | +| 13 | REPORT_ExportCSV | GET | `/api/reports/export/csv/{id}` | 200, CSV content | +| 14 | REPORT_ExportPDF | GET | `/api/reports/export/pdf/{id}` | 200, Content-Type: application/pdf | +| 15 | WORKENTRY_DeleteWorkEntry | DELETE | `/api/work-entries/{id}` | 200, "Work entry deleted successfully" | +| 16 | CLIENT_DeleteClientById | DELETE | `/api/clients/{id}` | 200, "Client deleted successfully" | +| 17 | HEALTH_Check | GET | `/health` | 200, body contains "OK" | + +## Prerequisites + +- **Java 8+** installed +- **Apache JMeter 5.x** installed ([download](https://jmeter.apache.org/download_jmeter.cgi)) +- **Backend running** on `localhost:3001` + +## Quick Start + +1. **Start the backend:** + ```bash + cd backend && npm install && npm start + ``` + +2. **Run the test (CLI mode):** + ```bash + jmeter -n -t jmeter/timesheet_api_test.jmx -l jmeter/results.jtl -e -o jmeter/report + ``` + +3. **View the HTML report:** + Open `jmeter/report/index.html` in a browser. + +## Configuration + +User-defined variables in the test plan (override via JMeter properties): + +| Variable | Default | Description | +|----------|---------|-------------| +| `USER_EMAIL` | `jmeter-test@example.com` | Test user email | +| `PROTOCOL` | `http` | Protocol | +| `SERVER` | `localhost` | Server hostname | +| `PORT` | `3001` | Server port | + +Override at runtime: +```bash +jmeter -n -t jmeter/timesheet_api_test.jmx \ + -JUSER_EMAIL=other@example.com \ + -JSERVER=staging.example.com \ + -JPORT=443 \ + -JPROTOCOL=https \ + -l jmeter/results.jtl -e -o jmeter/report +``` + +## Notes + +- Authentication uses the `x-user-email` header (not JWT). +- The test plan is self-cleaning: it creates and deletes its own test data. +- Thread Group is set to 1 thread / 1 loop for validation; increase for load testing. diff --git a/jmeter/timesheet_api_test.jmx b/jmeter/timesheet_api_test.jmx new file mode 100644 index 00000000..b5079a06 --- /dev/null +++ b/jmeter/timesheet_api_test.jmx @@ -0,0 +1,989 @@ + + + + + false + true + false + + + + USER_EMAIL + jmeter-test@example.com + = + + + PROTOCOL + http + = + + + SERVER + localhost + = + + + PORT + 3001 + = + + + + + + + + stoptest + + false + 1 + + 1 + 0 + false + + + true + + + + + + + Content-Type + application/json + + + x-user-email + ${USER_EMAIL} + + + + + + + + false + true + + + + true + + + + false + {"email":"${USER_EMAIL}"} + = + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/auth/login + POST + true + false + true + false + + + + LOGGED_IN_EMAIL + $.user.email + 1 + NOT_FOUND + + + + + 200 + 201 + + Expected response code 200 or 201 + Assertion.response_code + false + 40 + + + + + successful + + Response body should contain 'successful' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/auth/me + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + $.user.email + ${USER_EMAIL} + true + false + false + false + + + + + + + + false + true + + + + true + + + + false + {"name":"JMeter Test Client","description":"Client created by JMeter test","department":"Engineering","email":"client@testcorp.com"} + = + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/clients + POST + true + false + true + false + + + + CLIENT_ID + $.client.id + 1 + NOT_FOUND + + + + + 201 + + Expected response code 201 + Assertion.response_code + false + 8 + + + + + Client created successfully + + Response body should contain 'Client created successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/clients + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + clients + + Response body should contain 'clients' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/clients/${CLIENT_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + $.client.id + ${CLIENT_ID} + true + false + false + false + + + + + + + + false + true + + + + true + + + + false + {"name":"JMeter Updated Client","description":"Updated by JMeter","department":"QA","email":"updated-client@testcorp.com"} + = + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/clients/${CLIENT_ID} + PUT + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + Client updated successfully + + Response body should contain 'Client updated successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + true + + + + false + {"clientId":${CLIENT_ID},"hours":8,"description":"JMeter test work entry","date":"2026-05-12"} + = + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries + POST + true + false + true + false + + + + WORK_ENTRY_ID + $.workEntry.id + 1 + NOT_FOUND + + + + + 201 + + Expected response code 201 + Assertion.response_code + false + 8 + + + + + Work entry created successfully + + Response body should contain 'Work entry created successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + workEntries + + Response body should contain 'workEntries' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries?clientId=${CLIENT_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries/${WORK_ENTRY_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + + + + false + true + + + + true + + + + false + {"hours":4.5,"description":"Updated by JMeter","date":"2026-05-11"} + = + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries/${WORK_ENTRY_ID} + PUT + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + Work entry updated successfully + + Response body should contain 'Work entry updated successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/reports/client/${CLIENT_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + totalHours + + Response body should contain 'totalHours' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/reports/export/csv/${CLIENT_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + Date + + Response should contain CSV header content + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + + /api/reports/export/pdf/${CLIENT_ID} + GET + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + application/pdf + + Content-Type should contain application/pdf + Assertion.response_headers + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/work-entries/${WORK_ENTRY_ID} + DELETE + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + Work entry deleted successfully + + Response body should contain 'Work entry deleted successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /api/clients/${CLIENT_ID} + DELETE + true + false + true + false + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + Client deleted successfully + + Response body should contain 'Client deleted successfully' + Assertion.response_data + false + 2 + + + + + + + + false + true + + + + + + + ${SERVER} + ${PORT} + ${PROTOCOL} + UTF-8 + /health + GET + true + false + true + false + + + + + + Content-Type + application/json + + + + + + + 200 + + Expected response code 200 + Assertion.response_code + false + 8 + + + + + OK + + Response body should contain 'OK' + Assertion.response_data + false + 2 + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + true + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + true + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + true + true + 0 + true + true + true + true + true + true + + + + + + + +