feat(phase-2): separate /profile (identity) from /career (master profile)

Phase 2 — Career/Profile separation. The master career profile is the source of
truth; identity and career data are now saved independently so neither wipes the
other. CV Builder deliberately not built yet.

Backend — PUT /auth/profile is now a partial update:
- null/omitted field -> unchanged; "" -> cleared; value -> set (trimmed).
- Email/UserName never cleared to empty (login identifiers).
This lets /profile save identity fields and /career save the master-profile
fields through the same endpoint without one nulling the other. 4 new tests
cover the data-integrity guarantees (identity save keeps the CV, career save
keeps identity, empty clears, null leaves).

Frontend:
- ProfilePage save payload is now scoped by careerOnly: /career sends only
  { profileCvText, profileCvStructureJson }, /profile sends only identity.
- CareerWorkspacePage: removed the inert "CV Builder" tab (careerView) — Phase 2
  establishes the master profile only; the builder is Phase 4.
- Dropped the dead careerView prop.
- Updated the CV-save test to render career mode and assert identity is excluded.

Source-of-truth flip (CareerProfileService authoritative) stays deferred to F5
per the branch design; CareerProfileService keeps mirroring via its dual-write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-17 19:59:38 +02:00
parent a28c47f515
commit 66cc6a7db4
5 changed files with 132 additions and 39 deletions
+9 -4
View File
@@ -76,11 +76,11 @@ const structuredCv = {
],
};
function renderPage() {
function renderPage(props: { careerOnly?: boolean } = {}) {
return render(
<ToastProvider>
<I18nProvider>
<ProfilePage />
<ProfilePage {...props} />
</I18nProvider>
</ToastProvider>,
);
@@ -281,8 +281,10 @@ test('profile page rewrite tools use selected template and saved job context', a
await waitFor(() => expect(createObjectURLMock).toHaveBeenCalledTimes(REWRITE_TEMPLATES_COUNT));
});
test('saving profile persists structured cv json', async () => {
renderPage();
test('saving the master profile (career) persists structured cv json', async () => {
// Phase 2: the master-profile save lives on /career (careerOnly). It sends only the CV fields —
// identity is saved separately on /profile — so the payload carries profileCvStructureJson.
renderPage({ careerOnly: true });
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
const fullNameInput = screen.getByLabelText(/full name/i);
@@ -301,4 +303,7 @@ test('saving profile persists structured cv json', async () => {
expect(parsed.contact.fullName).toBe('Updated Demo User');
expect(parsed.skills).toEqual(['.NET', 'SQL']);
expect(parsed.jobs[0].title).toBe('System Developer');
// The career save must NOT carry identity fields (they belong to /profile).
expect(payload.email).toBeUndefined();
expect(payload.displayName).toBeUndefined();
});