Skip to content

Conversation

@Kavi-25
Copy link
Owner

@Kavi-25 Kavi-25 commented Aug 11, 2025

PR Type

Enhancement, Tests


Description

  • Created Locust load testing script.

  • Simulates user actions.

  • Includes login, data fetching, and leave application.

  • Uses CSV for user credentials.


Diagram Walkthrough

flowchart LR
  A[Employee.csv] --> B(locust.py);
  B --> C{Login};
  C --> D[Data Fetching];
  D --> E[Leave Application];
  E --> F[Check-in/Check-out];
  F --> G[Other Actions];
Loading

File Walkthrough

Relevant files
Tests
locust.py
Locust script for employee actions load testing                   

locust.py

  • Created a Locust load testing script (locust.py).
  • Implemented various user actions (login, data fetching, leave
    application, check-in/out).
  • Used CSV (Employee.csv) for user credentials.
  • Included error handling and logging.
+445/-0 

@Kavi-25
Copy link
Owner Author

Kavi-25 commented Aug 11, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
🧪 No relevant tests
🔒 Security concerns

Hardcoded Password:
The script uses a hardcoded password (COMMON_PW = "AVP@2023") in locust.py, line 10. This is a major security vulnerability. The password should never be hardcoded; instead, it should be retrieved from a secure environment variable or a dedicated secrets management system. This poses a significant risk if the code is exposed.

⚡ Recommended focus areas for review

Hardcoded Password

The script uses a hardcoded password (COMMON_PW = "AVP@2023"). This is a significant security risk. Consider using environment variables or a more secure method for managing credentials.

COMMON_PW = "AVP@2023"
Error Handling

The error handling in several functions (e.g., validate_resp, login, get_employee_details) could be improved. Currently, exceptions are sometimes printed to the console without detailed logging or more robust error handling.

def validate_resp(self, resp):
    try:
        resp.raise_for_status()
        # if resp.status_code >= 500:
        #     raise SystemError(resp.text)
        return True
    except Exception as e:
        print("[ERROR]", e)
        return False

def login(self):
    name = "login"
    method_name=f"Login / {self.username} / {datetime.now()}"
    resp = self.client.post(
        "/api/method/login",
        json={"usr": self.username, "pwd": self.password},
        headers={"Accept": "application/json", "Content-Type": "application/json"},
        name=method_name
    )
    if not self.validate_resp(resp):
        raise Exception(f"Login Failed: {resp.text} ({resp.status_code})")
    self.sid = resp.cookies.get("sid")
    if not self.sid:
        print(f"[WARN] No sid for {self.username}")
    else:
        print(f"[LOGIN] Success for {self.username}")

def get_employee_details(self):
    name = "get_employee_details"
    method_name=f"employee_details / {self.username} / {datetime.now()}"
    headers = {
        'Cookie': f'sid={self.sid}',
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    }
    payload = {'user_id': self.username}
    resp = self.client.post(
        "/api/method/get_preferences",
        headers=headers,
        json=payload,
        name=method_name
    )
    if not self.validate_resp(resp):
        raise Exception(f"get_employee_details failed: {resp.text} ({resp.status_code})")
    self.employee_data = resp.json().get("data")

def fetch_holiday_list(self):
    name = "fetch_holiday_list"
    if not self.employee_data:
        print("[SKIP] No employee data")
        return
    emp_id = self.employee_data.get("employee_id")
    if not emp_id:
        print("[SKIP] employee_id not found")
        return
    headers = {
        'Cookie': f'sid={self.sid}',
        'Accept': 'application/json',
    }
    resp = self.client.get(f"/api/resource/Employee/{emp_id}", headers=headers,
    name="fetch_holiday_list_employee"
    )
    if not self.validate_resp(resp):
        print(f"[ERROR] Holiday fetch failed: {resp.text}")
        return

    holiday_list = resp.json().get("data", {}).get("holiday_list")

    if holiday_list
        resp = self.client.get(f"/api/resource/Holiday List/{holiday_list}", headers=headers,name= name)
        if not self.validate_resp(resp):
            raise Exception(f"[ERROR] Holiday fetch failed: {resp.text}")

def fetch_salary_slip(self):
    name = "fetch_salary_slip"
    if not self.employee_data:
        print("[SKIP] No employee data")
        return
    emp_id = self.employee_data.get("employee_id")
    if not emp_id:
        print("employee id not found")
        return

    headers = {
        'Cookie': f'sid={self.sid}',
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    }

    payload = {
      "filters": [
        ["employee", "=", emp_id],
        ["custom_salary_month_and_year", "=", "April-2025"]
      ]
    }


    resp = self.client.get(f"/api/method/get_salary_slips", headers=headers,json = payload,
     name=name)
    if not self.validate_resp(resp):
        raise Exception(f"Salary slip fetch failed {resp.text}")

def get_leave_balance(self):
    name = "fetch_leave_balance"
    method_name = f"{name}/ {self.username} / {datetime.now()}" if USE_METHOD_NAME else f"POST_create_leave_application_draft"
    if not self.employee_data:
        print("[SKIP] No employee data")
        return
    emp_id = self.employee_data.get("employee_id")
    if not emp_id:
        print("employee id not found")
        return

    headers = {
        'Cookie': f'sid={self.sid}',
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    }

    payload = {
        "employee": emp_id
    }

    resp = self.client.get(f"/api/method/fetch_leave_balance",headers=headers, json=payload,
                           name=name)
    print(resp.text)
    if not self.validate_resp(resp):
        print(f"Getting Leave Application failed {resp.text}")
Date Range

The hardcoded date range in fetch_leave_list and compensatory_leavereq limits the scope of the test. Consider making this configurable.

        doctype = "Leave Application",
        name=method_name,
        filters=[
            ["employee", "!=", emp_id],
            ["from_date",">=", "2025-01-01"],
            ["to_date", "<=", "2025-07-12"]
        ],
        fields = [
        "name",
        "employee_name",
        "workflow_state",
        "from_date",
        "to_date",
        "total_leave_days",
        "leave_type",
        "description",
        "custom_is_hourly",
        "custom_from_date_time",
        "custom_to_date_time"
    ],
    )
    if not leave_list:
        print("no leave records")
    else:
        print(f"Found {len(leave_list)} leave applications.")

def compensatory_leavereq(self):
    method_name = f"POST_compensatory_leave_request / {self.username} / {datetime.now()}" if USE_METHOD_NAME else f"POST_compensatory_leave_request"
    if not self.employee_data:
        print("[skip] no employee details found")
        return
    emp_id = self.employee_data.get("employee_id")
    if not emp_id:
        print("[skip] no employee id found")
        return
    com_leavelist = self.get_list(
        doctype = "Compensatory Leave Request",
        name=method_name,
        filters =[
            ["employee", "!=", emp_id],
            ["work_from_date", ">=", dates.pop(0)],
            ["work_end_date", "<=", dates.pop(0)]
        ],
        fields = [
            "name",
            "employee_name",
            "docstatus",
            "work_from_date",
            "work_end_date",
            "custom_work_from_date_time",
            "custom_work_end_date_time",
            "leave_type"
        ],)

@Kavi-25
Copy link
Owner Author

Kavi-25 commented Aug 11, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Improve credential handling

The USER_CREDENTIALS list is modified in place using .pop(0). This might cause
issues if multiple locust users try to access the list concurrently. Consider using
a thread-safe mechanism to manage user credentials or a different data structure to
avoid race conditions.

locust.py [80-84]

 if not USER_CREDENTIALS:
     raise Exception("No user credentials left.")
-self.username, self.password = USER_CREDENTIALS.pop(0)
+credentials = USER_CREDENTIALS.pop(0)
+self.username, self.password = credentials
 print(f"[USER] {self.username}")
Suggestion importance[1-10]: 8

__

Why: The suggestion addresses a potential race condition in concurrent access to USER_CREDENTIALS. This is a significant improvement, enhancing the code's robustness and preventing unexpected behavior in a multi-threaded environment.

Medium
General
Fix inconsistent indentation

The code has inconsistent indentation. The if statements inside the get_list
function lack consistent indentation, potentially leading to unexpected behavior.
Correct the indentation to ensure proper code execution.

locust.py [60-70]

-if filters
+if filters:
     params['filters'] = json.dumps(filters)
 if fields:
     params['fields'] = json.dumps(fields)
 if order_by:
     params['order_by'] = order_by
 if limit_page_length != 0:
     params['limit_page_length'] = str(limit_page_length)
 if as_dict:
     params['as_dict'] = as_dict
Suggestion importance[1-10]: 3

__

Why: The indentation is inconsistent, but the suggested change is minor and doesn't affect functionality. The impact is low, improving only readability.

Low
Fix indentation and naming

The code lacks proper indentation after the if holiday_list: condition. This can
lead to logical errors. Ensure consistent indentation to correctly execute the code
block within the conditional statement. Also, consider using more descriptive
variable names for better readability.

locust.py [153-157]

-if holiday_list
-    resp = self.client.get(f"/api/resource/Holiday List/{holiday_list}", headers=headers,name= name)
+if holiday_list:
+    resp = self.client.get(f"/api/resource/Holiday List/{holiday_list}", headers=headers, name=name)
     if not self.validate_resp(resp):
         raise Exception(f"[ERROR] Holiday fetch failed: {resp.text}")
Suggestion importance[1-10]: 3

__

Why: The indentation is inconsistent, but the suggested change is minor and doesn't affect functionality. The impact is low, improving only readability. No naming improvements are actually suggested.

Low

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants