feat(career): wire /career to the relational profile API + completeness overview
Phase 3, frontend. /career now reads and writes the master profile through the
relational source of truth instead of the legacy blob path.
- CareerProfilePage loads GET /career/profile (structured profile from the
relational children + cvText + completeness) and saves PUT /career/profile
({ profile, cvText }). This keeps the relational store authoritative — the
previous PUT /auth/profile blob write left it stale after first load.
- Added a "Profile completeness" overview (percent bar + missing sections) at the
top of /career, from the server scorecard.
- PUT /career/profile now accepts { profile, cvText } so the single /career save
covers both the structured profile and the raw imported text; GET returns cvText.
Tests: career-save asserts the /career/profile payload; new completeness-overview
test; controller tests updated for the request wrapper. 75/76 frontend pass (the
1 failure is the unrelated pre-existing settings-view suite); prod build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,16 @@ void ProfilePage;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedApi.get.mockImplementation((url: string) => {
|
||||
if (url === '/career/profile') {
|
||||
// Phase 3: /career reads the structured profile from the relational source of truth.
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
profile: structuredCv,
|
||||
cvText: 'Professional Summary\nBuilt backend systems',
|
||||
completeness: { percent: 70, missing: ['Projects'], sections: [] },
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
if (url === '/auth/me') {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
@@ -289,9 +299,10 @@ test('profile page rewrite tools use selected template and saved job context', a
|
||||
await waitFor(() => expect(createObjectURLMock).toHaveBeenCalledTimes(REWRITE_TEMPLATES_COUNT));
|
||||
});
|
||||
|
||||
test('saving the master profile (career) persists structured cv json', async () => {
|
||||
// Phase 2: the master-profile save lives on /career (CareerProfilePage). It sends only the CV
|
||||
// fields — identity is saved separately on /profile — so the payload carries profileCvStructureJson.
|
||||
test('saving the master profile (career) persists the structured profile via /career/profile', async () => {
|
||||
// Phase 3: /career saves the master profile through the relational API. The payload carries the
|
||||
// structured profile object (not the legacy flat blob) and never identity fields.
|
||||
mockedApi.put.mockResolvedValue({ data: { profile: structuredCv, completeness: { percent: 70, missing: [], sections: [] }, cvText: '' } } as any);
|
||||
renderWith(CareerProfilePage);
|
||||
|
||||
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
|
||||
@@ -303,15 +314,20 @@ test('saving the master profile (career) persists structured cv json', async ()
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedApi.put).toHaveBeenCalled();
|
||||
expect(mockedApi.put).toHaveBeenCalledWith('/career/profile', expect.anything());
|
||||
});
|
||||
|
||||
const payload = mockedApi.put.mock.calls[0][1] as any;
|
||||
const parsed = JSON.parse(payload.profileCvStructureJson);
|
||||
expect(parsed.contact.fullName).toBe('Updated Demo User');
|
||||
expect(parsed.skills).toEqual(['.NET', 'SQL']);
|
||||
expect(parsed.jobs[0].title).toBe('System Developer');
|
||||
expect(payload.profile.contact.fullName).toBe('Updated Demo User');
|
||||
expect(payload.profile.skills).toEqual(['.NET', 'SQL']);
|
||||
expect(payload.profile.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();
|
||||
});
|
||||
|
||||
test('/career shows the profile completeness overview', async () => {
|
||||
renderWith(CareerProfilePage);
|
||||
expect(await screen.findByText(/profile completeness/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/70%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user