File size: 11,375 Bytes
8155d78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/usr/bin/env python3
"""Deterministic regression for Phase 08-02: Non-destructive tailoring (R17).

It MUST be impossible for this script to pass while the candidate's real history
can be altered. It proves:

  1. STATIC: every bullet-mutation call site in resume_customizer.py
     (_weave_keywords_into_bullets / _force_weave_into_bullets) is gated by
     `non_destructive`, and _maximize_external_coverage takes a non_destructive
     parameter. (Adding a new unguarded mutation site fails this.)
  2. END-TO-END: _generate_resume_v4 run with a destructive no-LLM stub provider
     and _maximum_ats_mode=True yields a DOCX whose role titles, companies,
     dates, and EXISTING bullets are byte-identical to the base resume, the
     destructive LLM output is discarded, and no fabrication term appears.
  3. UNIT: _apply_non_destructive preserves roles verbatim, appends <=3 bullets
     per role, and never appends a fabrication-risk term (cissp/pmp/cuda).
  4. LATEX: inject_keywords appends new \\item lines (no in-place edits), keeps
     one competencies line + a summary sentence, and is idempotent.

No real LLM, no LaTeX engine, no network. ASCII-only output. Exit 0 on success.
"""

import os
import re
import sys
import tempfile

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if REPO_ROOT not in sys.path:
    sys.path.insert(0, REPO_ROOT)

CUST_PATH = os.path.join(REPO_ROOT, "src", "resume_customizer.py")

_failures = []


def check(condition, message):
    if condition:
        print(f"  [PASS] {message}")
    else:
        print(f"  [FAIL] {message}")
        _failures.append(message)


# ── 1. STATIC call-site guard check ─────────────────────────────────────────
def test_static_guards():
    print("[1] Static: all bullet-mutation call sites gated by non_destructive")
    src = open(CUST_PATH, encoding="utf-8").read()
    check("NON_DESTRUCTIVE_DEFAULT" in src and "_apply_non_destructive" in src
          and "_append_keyword_bullets" in src,
          "non-destructive mode + append helpers present")
    check(bool(re.search(r"def _maximize_external_coverage\([^)]*non_destructive",
                         src, re.S)),
          "_maximize_external_coverage takes a non_destructive parameter")
    calls = [m.start()
             for c in ("_weave_keywords_into_bullets(", "_force_weave_into_bullets(")
             for m in re.finditer(re.escape(c), src)]
    unguarded = [i for i in calls if "non_destructive" not in src[max(0, i - 400):i]]
    check(not unguarded,
          f"every weave/force-weave occurrence ({len(calls)}) is non_destructive-guarded")


# ── 2. END-TO-END diff through the live max-ATS path ────────────────────────
def _build_base_resume():
    from src.resume_model import Resume, Role, Contact, Education
    return Resume(
        name="Jordan Tester",
        contact=Contact(email="jordan@example.com", phone="555-0100",
                        location="Hyderabad, Telangana, India"),
        summary="Product manager with delivery experience across teams.",
        skills=[],
        roles=[
            Role(title="Senior Product Manager", company="Acme Qwerty Labs",
                 location="Remote", dates="Jan 2021 - Present",
                 bullets=[
                     "Spearheaded zylotron onboarding revamp lifting activation by forty percent.",
                     "Owned blorptastic pricing experiments across enterprise cohorts.",
                     "Led quibblefax vendor integration from scoping to launch.",
                 ]),
            Role(title="Associate Product Owner", company="Bumblewick Systems",
                 location="Pune", dates="Jun 2018 - Dec 2020",
                 bullets=[
                     "Drove frobnicator dashboard adoption to scale across regions.",
                     "Managed wuggle backlog and release cadence for two squads.",
                     "Shipped znorf analytics module improving retention.",
                 ]),
        ],
        education=[Education(degree="MBA", institution="Northwind Institute",
                             dates="2017")],
    )


class _StubProvider:
    """No-LLM provider that RETURNS DESTRUCTIVE output (renamed titles + rewritten
    bullets) to prove the pipeline discards it in non-destructive mode."""
    name = "stub"

    def __init__(self, destructive_dict):
        self._d = destructive_dict

    def tailor_resume(self, base_dict, jd, title, company, raw):
        return self._d, "ok"


def test_end_to_end():
    print("[2] End-to-end: _generate_resume_v4 (_maximum_ats_mode=True) preserves history")
    from src.resume_customizer import ResumeCustomizer, _read_docx_text

    base = _build_base_resume()
    dd = base.to_dict()
    dd["summary"] = "REWRITTENSUMMARY tailored pitch."
    for i, r in enumerate(dd["roles"]):
        r["title"] = f"RENAMEDTITLE{i} Chief Officer"
        r["bullets"] = [f"REWRITTENBULLET{i}A delivered value",
                        f"REWRITTENBULLET{i}B drove growth"]
    stub = _StubProvider(dd)

    jd = ("We need product roadmap ownership, stakeholder management, and a/b "
          "testing experience for a SaaS B2B product. Strong user research and "
          "go-to-market skills required.")
    tmp = tempfile.mkdtemp(prefix="nd_e2e_")
    try:
        cust = ResumeCustomizer(None, base.to_flat_text(), tmp)
        fp = os.path.join(cust.output_dir, "out.docx")
        job = {
            "title": "Product Manager", "company": "Northwind",
            "description": jd, "ats_keywords": "", "_raw_assessment": {},
            "_maximum_ats_mode": True, "_confirmed_terms": [],
        }
        path = cust._generate_resume_v4(job, cfg=None, filepath=fp,
                                        provider=stub, base_resume_override=base)
        check(bool(path) and os.path.exists(path),
              "generation returned a DOCX path")
        if not path or not os.path.exists(path):
            return
        text = _read_docx_text(path)
        low = text.lower()

        # Titles / companies / dates verbatim.
        for token in ("Senior Product Manager", "Associate Product Owner",
                      "Acme Qwerty Labs", "Bumblewick Systems"):
            check(token in text, f"verbatim preserved: '{token}'")
        check("2021" in text and "2018" in text, "employment dates preserved")

        # Existing bullets verbatim (distinctive tokens).
        for token in ("zylotron", "blorptastic", "quibblefax",
                      "frobnicator", "wuggle", "znorf"):
            check(token in low, f"existing bullet token preserved: '{token}'")

        # Destructive ROLE output discarded (renamed titles + rewritten bullets).
        # NOTE: R17 PERMITS summary augmentation, so the tailored summary may
        # legitimately differ β€” only role content must be preserved verbatim.
        for bad in ("renamedtitle", "rewrittenbullet"):
            check(bad not in low, f"destructive role output discarded: '{bad}'")

        # No fabrication anywhere.
        for fab in ("cissp", "pmp", "cuda", "12+ years"):
            check(fab not in low, f"no fabrication term in export: '{fab}'")
    finally:
        import shutil
        shutil.rmtree(tmp, ignore_errors=True)


# ── 3. UNIT: _apply_non_destructive ─────────────────────────────────────────
def test_apply_non_destructive_unit():
    print("[3] Unit: _apply_non_destructive preserves roles + caps appended bullets")
    from src.resume_customizer import ResumeCustomizer
    from src.resume_model import Resume

    base = _build_base_resume()
    # A 'destructive' tailored copy with renamed titles + rewritten bullets.
    dd = base.to_dict()
    for i, r in enumerate(dd["roles"]):
        r["title"] = f"FAKE{i}"
        r["bullets"] = [f"FAKEBULLET{i}"]
    tailored = Resume.from_dict(dd)

    tmp = tempfile.mkdtemp(prefix="nd_unit_")
    try:
        cust = ResumeCustomizer(None, base.to_flat_text(), tmp)
        cust._apply_non_destructive(
            tailored, base,
            include_terms=["product roadmap", "stakeholder management",
                           "a/b testing", "cissp", "pmp", "cuda"],
            jd_text="product roadmap stakeholder management a/b testing",
        )
        for ti, role in enumerate(tailored.roles):
            bro = base.roles[ti]
            check(role.title == bro.title and role.company == bro.company
                  and role.dates == bro.dates,
                  f"role {ti}: title/company/dates verbatim")
            check(role.bullets[:len(bro.bullets)] == list(bro.bullets),
                  f"role {ti}: existing bullets verbatim")
            extra = len(role.bullets) - len(bro.bullets)
            check(0 <= extra <= 3, f"role {ti}: <=3 bullets appended ({extra})")
            appended = " ".join(role.bullets[len(bro.bullets):]).lower()
            for fab in ("cissp", "pmp", "cuda"):
                check(fab not in appended,
                      f"role {ti}: no fabrication term appended ('{fab}')")
    finally:
        import shutil
        shutil.rmtree(tmp, ignore_errors=True)


# ── 4. LATEX preservation ───────────────────────────────────────────────────
SAMPLE_LATEX = (
    "\\documentclass{article}\n\\begin{document}\n"
    "\\section*{Summary}\nExperienced PM.\n"
    "\\section*{Experience}\n\\begin{itemize}\n"
    "\\item Led onboarding discovery research.\n"
    "\\item Shipped the v1 analytics module.\n"
    "\\end{itemize}\n\\end{document}\n"
)


def test_latex_preservation():
    print("[4] LaTeX: inject_keywords appends items, no in-place edits, idempotent")
    from src.latex_resume import inject_keywords
    terms = ["product strategy", "stakeholder management", "a/b testing",
             "user research"]
    out, inj = inject_keywords(SAMPLE_LATEX, terms)
    check("Led onboarding discovery research." in out
          and "Shipped the v1 analytics module." in out,
          "existing \\item lines unchanged")
    check("(applying" not in out, "no in-place '(applying X)' edits")
    n = out.count("% ats-item")
    check(1 <= n <= 3, f"1..3 appended \\item lines ({n})")
    check(out.count("\\textbf{Core Competencies:}") <= 1,
          "at most one competencies line")
    check("core focus areas include" in out.lower(), "summary sentence present")
    out2, _ = inject_keywords(out, terms)
    check(out2.count("% ats-item") == n, "idempotent (re-run does not stack items)")


def main():
    print("=" * 70)
    print("Phase 08-02 verification: Non-destructive tailoring (R17)")
    print("=" * 70)
    test_static_guards()
    test_end_to_end()
    test_apply_non_destructive_unit()
    test_latex_preservation()
    print("-" * 70)
    if _failures:
        print(f"FAIL - {len(_failures)} check(s) failed:")
        for f in _failures:
            print(f"  - {f}")
        return 1
    print("PASS - tailoring is non-destructive end-to-end (history preserved, "
          "keywords appended, honesty intact)")
    return 0


if __name__ == "__main__":
    sys.exit(main())