"""Unit tests for g_sheet/plugins.py — mapping against the real 32-column sheet. No DB, no network. Pins header aliases, parsers, and the Bilal sample row. """ from __future__ import annotations from datetime import datetime, timezone from g_sheet import plugins from g_sheet.enums import FORM_DATA_FIELDS, HEADER_ALIASES, FormDataField from g_sheet.models import FormData # Real header row from "PK - Recruitment Tracking Sheet" (32 columns). HEADERS = [ "um", "Year", "Month", "Date", "Time of Entry", "Screened By", "Candidate Name", "HR comments", "Candidate Number", "Candidate Email", "Profile Link", "Area of Expertise", "Requisition Number", "Position Suitable For", "Source of Application", "Age", "Marital Status", "Education", "University of Graduation ", "Experience", "Experience Details", "Area of Residence", "Communication Skills\n(01 to 10)", "Preferred Timings?", "Availability to work in the H.O", "Current Company", "Reason for Leaving", "How soon can you join", "Current Salary", "Expected Salary", "Pros", "Cons", ] EXPECTED_FIELDS = ( FormDataField.SERIAL_NO, FormDataField.ENTRY_YEAR, FormDataField.ENTRY_MONTH, FormDataField.ENTRY_DATE, FormDataField.ENTRY_TIME, FormDataField.SCREENED_BY, FormDataField.NAME, FormDataField.HR_COMMENTS, FormDataField.CANDIDATE_NUMBER, FormDataField.CANDIDATE_EMAIL, FormDataField.PROFILE_LINK, FormDataField.AREA_OF_EXPERTISE, FormDataField.REQUISITION_NUMBER, FormDataField.POSITION_SUITABLE_FOR, FormDataField.SOURCE_OF_APPLICATION, FormDataField.AGE, FormDataField.MARITAL_STATUS, FormDataField.DEGREE, FormDataField.UNIVERSITY, FormDataField.EXPERIENCE, FormDataField.EXPERIENCE_DETAILS, FormDataField.AREA_OF_RESIDENCE, FormDataField.COMMUNICATION_SKILLS, FormDataField.PREFERRED_TIMINGS, FormDataField.HO_AVAILABILITY, FormDataField.CURRENT_COMPANY, FormDataField.REASON_FOR_LEAVING, FormDataField.NOTICE_PERIOD, FormDataField.CURRENT_SALARY, FormDataField.EXPECTED_SALARY, FormDataField.PROS, FormDataField.CONS, ) SAMPLE_RECORD = { "um": "1", "Year": "2021", "Month": "June", "Date": "23-Jun-2021", "Time of Entry": "10:30 AM", "Screened By": "Sara", "Candidate Name": "Muhammad Bilal Khan", "HR comments": "Good profile", "Candidate Number": "0303-2892503", "Candidate Email": "bilal_kf@yahoo.com", "Profile Link": "https://example.com/bilal", "Area of Expertise": "Software Development", "Requisition Number": "REQ-1", "Position Suitable For": "Backend Engineer", "Source of Application": "Referral", "Age": "33", "Marital Status": "Married", "Education": "BS CS", "University of Graduation ": "NUST", "Experience": "11 Years", "Experience Details": "Software development related experience", "Area of Residence": "Islamabad", "Communication Skills\n(01 to 10)": "7", "Preferred Timings?": "Morning", "Availability to work in the H.O": "Yes", "Current Company": "Acme", "Reason for Leaving": "Growth", "How soon can you join": "1 month", "Current Salary": "110k", "Expected Salary": "150k", "Pros": "Strong backend", "Cons": "Limited cloud", } def test_every_real_header_maps_and_none_are_unmapped(): assert len(HEADERS) == 32 for header, field in zip(HEADERS, EXPECTED_FIELDS): assert plugins.match_field(header) == field, header assert plugins.collect_unmapped_headers(HEADERS) == [] def test_experience_details_does_not_overwrite_experience(): mapped = plugins.map_record_to_form_data("tab", SAMPLE_RECORD, HEADERS, 2) assert mapped["experience"] == "11 Years" assert mapped["experience_details"] == "Software development related experience" def test_candidate_number_and_email_land_in_own_columns(): mapped = plugins.map_record_to_form_data("tab", SAMPLE_RECORD, HEADERS, 2) assert mapped["candidate_number"] == "0303-2892503" assert mapped["candidate_email"] == "bilal_kf@yahoo.com" assert mapped["name"] == "Muhammad Bilal Khan" def test_date_maps_to_entry_date_not_interview_round(): mapped = plugins.map_record_to_form_data("tab", SAMPLE_RECORD, HEADERS, 2) assert mapped["entry_date"] == datetime(2021, 6, 23, tzinfo=timezone.utc) assert "interview_date" not in mapped def test_marital_status_only_lands_in_marital_status(): mapped = plugins.map_record_to_form_data("tab", SAMPLE_RECORD, HEADERS, 2) assert mapped["marital_status"] == "Married" assert "family_details" not in mapped assert "interview_status" not in mapped def test_canonical_header_strips_parens_question_and_trailing_space(): assert plugins.canonical_header("Communication Skills\n(01 to 10)") == "communication skills" assert plugins.canonical_header("Preferred Timings?") == "preferred timings" assert plugins.canonical_header("University of Graduation ") == "university of graduation" def test_parse_salary_and_score(): assert plugins.parse_salary("110k") == 110000 assert plugins.parse_salary("60k-70k") == 60000 assert plugins.parse_salary("Negotiable") is None assert plugins.parse_score("7") == 7 assert plugins.parse_score("15") is None def test_rows_to_indexed_records_keeps_true_sheet_row_across_blank(): rows = [ ["Name", "Age"], ["Ada", "30"], ["", ""], ["Bob", "40"], ] indexed = plugins.rows_to_indexed_records(rows) assert indexed == [ (2, {"Name": "Ada", "Age": "30"}), (4, {"Name": "Bob", "Age": "40"}), ] # read API contract still drops blanks without exposing indices assert plugins.rows_to_records(rows) == [ {"Name": "Ada", "Age": "30"}, {"Name": "Bob", "Age": "40"}, ] def test_no_alias_string_appears_under_two_fields(): seen: dict[str, FormDataField] = {} for field, aliases in HEADER_ALIASES.items(): for alias in aliases: assert alias not in seen, f"{alias!r} under {seen[alias]} and {field}" seen[alias] = field def test_form_data_fields_match_model(): assert set(FORM_DATA_FIELDS) == set(FormData.model_fields) def test_sample_row_end_to_end(): mapped = plugins.map_record_to_form_data( "PK - Recruitment Tracking Sheet", SAMPLE_RECORD, HEADERS, 2, ) assert mapped["experience"] == "11 Years" assert mapped["experience_details"] == "Software development related experience" assert mapped["candidate_email"] == "bilal_kf@yahoo.com" assert mapped["age"] == 33 assert mapped["current_salary_value"] == 110000 assert mapped["communication_skills"] == 7 assert mapped["pros"] == "Strong backend" assert mapped["cons"] == "Limited cloud" assert mapped["row_number"] == 2 assert mapped["sheet"] == "PK - Recruitment Tracking Sheet" assert mapped.get("job_post_id") is None # Google Form Responses tab — headers differ from the PK screening sheet. FORM_RESPONSE_HEADERS = [ "Timestamp", "Email", "Full Name", "Gender", "Marital Status", "University", "Educational Degree", "Year of Graduation", "Position Applied For", "LinkedIn Profile Link", "Drop your updated resume", "How soon can you join us?", "Phone number (03XX-XXXXXXX)", "Are you willing to relocate?", "Residing City", "Residing Country", "Area of Interest", "Recruiter", "Where did you hear about the position you're applying for?", ] FORM_RESPONSE_RECORD = { "Timestamp": "7/2/2026 17:50:19", "Email": "nusratazra@gmail.com", "Full Name": "Nusrat Azra", "Gender": "Female", "Marital Status": "Single", "University": "Karachi University", "Educational Degree": "Masters", "Year of Graduation": "12/2/2007", "Position Applied For": "Executive Secretary", "LinkedIn Profile Link": "https://www.linkedin.com/in/nusrat-azra-executive-manager-to-c-suite-3bb86719/", "Drop your updated resume": "https://drive.google.com/open?id=1l5HOY5R6KiL6270A_sV56FkdX6EIGfI4", "How soon can you join us?": "1 - 2 weeks", "Phone number (03XX-XXXXXXX)": "03312464228", "Are you willing to relocate?": "Yes", "Residing City": "Karachi", "Residing Country": "Pakistan", "Area of Interest": "", "Recruiter": "", "Where did you hear about the position you're applying for?": "Indeed", } def test_form_response_headers_map_to_typed_columns(): mapped = plugins.map_record_to_form_data( "Form Responses - Candidate Database Sheet 2026", FORM_RESPONSE_RECORD, FORM_RESPONSE_HEADERS, 9, ) assert mapped["name"] == "Nusrat Azra" assert mapped["candidate_email"] == "nusratazra@gmail.com" assert mapped["candidate_number"] == "03312464228" assert mapped["degree"] == "Masters" assert mapped["university"] == "Karachi University" assert mapped["position_suitable_for"] == "Executive Secretary" assert mapped["notice_period"] == "1 - 2 weeks" assert mapped["source_of_application"] == "Indeed" assert mapped["ho_availability"] == "Yes" assert mapped["marital_status"] == "Single" assert mapped["area_of_residence"] == "Karachi" # first matching residence header wins assert mapped["profile_link"] == ( "https://www.linkedin.com/in/nusrat-azra-executive-manager-to-c-suite-3bb86719/" ) assert mapped["entry_year"] == "12/2/2007" assert mapped["entry_date"] is not None assert mapped["entry_time"] == "17:50:19" # Timestamp must never be used as the candidate name. assert mapped["name"] != "7/2/2026 17:50:19" assert mapped["job_post_id"] is None assert mapped["raw_record"]["Full Name"] == "Nusrat Azra"