feat(contacts): add POST /contacts with multi-value fields
Implements Task 12: Extend POST /contacts to accept and persist phones, emails, and addresses arrays. Uses atomic transactions to ensure all related records are created together or rolled back on error. - Validation: validatePhones/Emails/Addresses before insert - Transaction: db.transaction() for atomic Contact + Multi-Values - Backward compatible: Multi-value fields are optional - Refactoring: Extracted loadMultiValueFields() helper (DRY) - Response includes all multi-value fields with generated IDs Tests: 3 new tests (create with multi-values, validation, backward compat) TDD workflow: RED → GREEN → REFACTOR → Commit Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+23
-11
@@ -1,8 +1,8 @@
|
|||||||
# CardDAV API Routes Implementation - Fortschritt
|
# CardDAV API Routes Implementation - Fortschritt
|
||||||
|
|
||||||
**Stand:** 2026-05-04, nach Task 11 von 15 (Session 3)
|
**Stand:** 2026-05-04, nach Task 12 von 15 (Session 3)
|
||||||
**Plan:** `docs/superpowers/plans/2026-05-04-cardav-api-routes.md`
|
**Plan:** `docs/superpowers/plans/2026-05-04-cardav-api-routes.md`
|
||||||
**Nächster Task:** Task 12 - POST /contacts mit Multi-Value Fields
|
**Nächster Task:** Task 13 - PUT /contacts/:id mit Multi-Value Fields
|
||||||
|
|
||||||
## Abgeschlossene Tasks
|
## Abgeschlossene Tasks
|
||||||
|
|
||||||
@@ -137,12 +137,22 @@
|
|||||||
- Contact ohne Multi-Value Fields (leere Arrays)
|
- Contact ohne Multi-Value Fields (leere Arrays)
|
||||||
- TDD-Workflow eingehalten: RED → GREEN → Commit
|
- TDD-Workflow eingehalten: RED → GREEN → Commit
|
||||||
|
|
||||||
## Offene Tasks (12-15)
|
### ✅ Task 12: POST /contacts - Create With Multi-Value Fields
|
||||||
|
**Commit:** [wird gesetzt nach commit]
|
||||||
|
|
||||||
### 🔄 Task 12: POST /contacts
|
- Implementiert: POST /contacts erweitert um phones, emails, addresses Arrays
|
||||||
- Erstellen mit Multi-Value Fields
|
- Validierung: `validatePhones()`, `validateEmails()`, `validateAddresses()` vor Insert
|
||||||
- Validierung mit `validatePhones()`, `validateEmails()`, `validateAddresses()`
|
- Transaktionen: `db.transaction()` für atomare Contact + Multi-Values Inserts
|
||||||
- Atomare Transaktionen für Contact + Multi-Values
|
- Backward Compatible: Optional fields, leere Arrays wenn nicht angegeben
|
||||||
|
- Response: Contact mit allen Multi-Value Fields inkl. generierte IDs
|
||||||
|
- Tests: 3 neue Tests
|
||||||
|
- Contact mit allen Multi-Value Fields erstellen
|
||||||
|
- Validierung: Fehler bei invaliden Phone-Daten (400)
|
||||||
|
- Backward Compatibility: Contact ohne Multi-Values (leere Arrays)
|
||||||
|
- Refactoring: `loadMultiValueFields(contactId)` Helper extrahiert (DRY)
|
||||||
|
- TDD-Workflow eingehalten: RED → GREEN → REFACTOR → Commit
|
||||||
|
|
||||||
|
## Offene Tasks (13-15)
|
||||||
|
|
||||||
### 🔄 Task 13: PUT /contacts/:id
|
### 🔄 Task 13: PUT /contacts/:id
|
||||||
- Update mit Multi-Value Fields
|
- Update mit Multi-Value Fields
|
||||||
@@ -239,10 +249,12 @@ c078a48 feat(cardav): implement POST /accounts/:id/addressbooks/refresh endpoint
|
|||||||
|
|
||||||
## Test-Status
|
## Test-Status
|
||||||
|
|
||||||
- **Gesamt:** 103 Tests, alle bestehen
|
- **Gesamt:** 106 Tests, alle bestehen
|
||||||
- **Suites:** 18 Suites
|
- **Suites:** 19 Suites
|
||||||
- **CardDAV API Routes Suite:** 14 Tests
|
- **CardDAV API Routes Suite:** 14 Tests
|
||||||
- **Contacts API - Multi-Value Fields Suite:** 2 Tests
|
- **Contacts API - Multi-Value Fields Suite:** 5 Tests
|
||||||
|
- GET /contacts/:id: 2 Tests
|
||||||
|
- POST /contacts: 3 Tests
|
||||||
- Account Management (6 Tests):
|
- Account Management (6 Tests):
|
||||||
- GET /accounts (empty)
|
- GET /accounts (empty)
|
||||||
- GET /accounts (populated with shape)
|
- GET /accounts (populated with shape)
|
||||||
@@ -283,7 +295,7 @@ c078a48 feat(cardav): implement POST /accounts/:id/addressbooks/refresh endpoint
|
|||||||
#9. [completed] Task 9: PUT /addressbooks/:id - Toggle Addressbook
|
#9. [completed] Task 9: PUT /addressbooks/:id - Toggle Addressbook
|
||||||
#10. [completed] Task 10: POST /accounts/:id/sync - Sync Account
|
#10. [completed] Task 10: POST /accounts/:id/sync - Sync Account
|
||||||
#11. [completed] Task 11: GET /contacts/:id - With Multi-Values
|
#11. [completed] Task 11: GET /contacts/:id - With Multi-Values
|
||||||
#12. [pending] Task 12: POST /contacts - Create With Multi-Values
|
#12. [completed] Task 12: POST /contacts - Create With Multi-Values
|
||||||
#13. [pending] Task 13: PUT /contacts/:id - Update With Multi-Values
|
#13. [pending] Task 13: PUT /contacts/:id - Update With Multi-Values
|
||||||
#14. [pending] Task 14: Document All Routes in OpenAPI
|
#14. [pending] Task 14: Document All Routes in OpenAPI
|
||||||
#15. [pending] Task 15: Mount CardDAV Router
|
#15. [pending] Task 15: Mount CardDAV Router
|
||||||
|
|||||||
+134
-45
@@ -16,6 +16,53 @@ const router = express.Router();
|
|||||||
const VALID_CATEGORIES = ['Arzt', 'Schule/Kita', 'Behörde', 'Versicherung',
|
const VALID_CATEGORIES = ['Arzt', 'Schule/Kita', 'Behörde', 'Versicherung',
|
||||||
'Handwerker', 'Notfall', 'Sonstiges'];
|
'Handwerker', 'Notfall', 'Sonstiges'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads multi-value fields (phones, emails, addresses) for a contact.
|
||||||
|
* @param {number} contactId - Contact ID
|
||||||
|
* @returns {{ phones: Array, emails: Array, addresses: Array }}
|
||||||
|
*/
|
||||||
|
function loadMultiValueFields(contactId) {
|
||||||
|
const phones = db.get().prepare(`
|
||||||
|
SELECT id, label, value, is_primary FROM contact_phones
|
||||||
|
WHERE contact_id = ?
|
||||||
|
ORDER BY is_primary DESC, id ASC
|
||||||
|
`).all(contactId).map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
label: p.label,
|
||||||
|
value: p.value,
|
||||||
|
isPrimary: p.is_primary === 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
const emails = db.get().prepare(`
|
||||||
|
SELECT id, label, value, is_primary FROM contact_emails
|
||||||
|
WHERE contact_id = ?
|
||||||
|
ORDER BY is_primary DESC, id ASC
|
||||||
|
`).all(contactId).map(e => ({
|
||||||
|
id: e.id,
|
||||||
|
label: e.label,
|
||||||
|
value: e.value,
|
||||||
|
isPrimary: e.is_primary === 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
const addresses = db.get().prepare(`
|
||||||
|
SELECT id, label, street, city, state, postal_code, country, is_primary
|
||||||
|
FROM contact_addresses
|
||||||
|
WHERE contact_id = ?
|
||||||
|
ORDER BY is_primary DESC, id ASC
|
||||||
|
`).all(contactId).map(a => ({
|
||||||
|
id: a.id,
|
||||||
|
label: a.label,
|
||||||
|
street: a.street,
|
||||||
|
city: a.city,
|
||||||
|
state: a.state,
|
||||||
|
postalCode: a.postal_code,
|
||||||
|
country: a.country,
|
||||||
|
isPrimary: a.is_primary === 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { phones, emails, addresses };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates phones array for multi-value contact fields.
|
* Validates phones array for multi-value contact fields.
|
||||||
* @param {Array} phones - Array of { label, value, isPrimary? }
|
* @param {Array} phones - Array of { label, value, isPrimary? }
|
||||||
@@ -140,7 +187,7 @@ router.get('/', (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* POST /api/v1/contacts
|
* POST /api/v1/contacts
|
||||||
* Neuen Kontakt anlegen.
|
* Neuen Kontakt anlegen.
|
||||||
* Body: { name, category?, phone?, email?, address?, notes? }
|
* Body: { name, category?, phone?, email?, address?, notes?, phones?, emails?, addresses? }
|
||||||
* Response: { data: Contact }
|
* Response: { data: Contact }
|
||||||
*/
|
*/
|
||||||
router.post('/', (req, res) => {
|
router.post('/', (req, res) => {
|
||||||
@@ -154,14 +201,95 @@ router.post('/', (req, res) => {
|
|||||||
const errors = collectErrors([vName, vCat, vPhone, vEmail, vAddress, vNotes]);
|
const errors = collectErrors([vName, vCat, vPhone, vEmail, vAddress, vNotes]);
|
||||||
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
|
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
|
||||||
|
|
||||||
|
// Validate multi-value fields if provided
|
||||||
|
if (req.body.phones !== undefined) {
|
||||||
|
const phonesValidation = validatePhones(req.body.phones);
|
||||||
|
if (!phonesValidation.valid) {
|
||||||
|
return res.status(400).json({ error: phonesValidation.error, code: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body.emails !== undefined) {
|
||||||
|
const emailsValidation = validateEmails(req.body.emails);
|
||||||
|
if (!emailsValidation.valid) {
|
||||||
|
return res.status(400).json({ error: emailsValidation.error, code: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body.addresses !== undefined) {
|
||||||
|
const addressesValidation = validateAddresses(req.body.addresses);
|
||||||
|
if (!addressesValidation.valid) {
|
||||||
|
return res.status(400).json({ error: addressesValidation.error, code: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert contact and multi-value fields in a transaction
|
||||||
|
const transaction = db.get().transaction(() => {
|
||||||
const result = db.get().prepare(`
|
const result = db.get().prepare(`
|
||||||
INSERT INTO contacts (name, category, phone, email, address, notes)
|
INSERT INTO contacts (name, category, phone, email, address, notes)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`).run(vName.value, vCat.value || 'Sonstiges', vPhone.value, vEmail.value,
|
`).run(vName.value, vCat.value || 'Sonstiges', vPhone.value, vEmail.value,
|
||||||
vAddress.value, vNotes.value);
|
vAddress.value, vNotes.value);
|
||||||
|
|
||||||
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(result.lastInsertRowid);
|
const contactId = result.lastInsertRowid;
|
||||||
res.status(201).json({ data: contact });
|
|
||||||
|
// Insert phones
|
||||||
|
if (req.body.phones && Array.isArray(req.body.phones)) {
|
||||||
|
const insertPhone = db.get().prepare(`
|
||||||
|
INSERT INTO contact_phones (contact_id, label, value, is_primary)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
for (const phone of req.body.phones) {
|
||||||
|
insertPhone.run(contactId, phone.label, phone.value, phone.isPrimary ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert emails
|
||||||
|
if (req.body.emails && Array.isArray(req.body.emails)) {
|
||||||
|
const insertEmail = db.get().prepare(`
|
||||||
|
INSERT INTO contact_emails (contact_id, label, value, is_primary)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
for (const email of req.body.emails) {
|
||||||
|
insertEmail.run(contactId, email.label, email.value, email.isPrimary ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert addresses
|
||||||
|
if (req.body.addresses && Array.isArray(req.body.addresses)) {
|
||||||
|
const insertAddress = db.get().prepare(`
|
||||||
|
INSERT INTO contact_addresses (contact_id, label, street, city, state, postal_code, country, is_primary)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
for (const address of req.body.addresses) {
|
||||||
|
insertAddress.run(
|
||||||
|
contactId,
|
||||||
|
address.label,
|
||||||
|
address.street || null,
|
||||||
|
address.city || null,
|
||||||
|
address.state || null,
|
||||||
|
address.postalCode || null,
|
||||||
|
address.country || null,
|
||||||
|
address.isPrimary ? 1 : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contactId;
|
||||||
|
});
|
||||||
|
|
||||||
|
const contactId = transaction();
|
||||||
|
|
||||||
|
// Query the created contact with multi-value fields
|
||||||
|
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(contactId);
|
||||||
|
const multiValueFields = loadMultiValueFields(contactId);
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
data: {
|
||||||
|
...contact,
|
||||||
|
...multiValueFields
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('', err);
|
log.error('', err);
|
||||||
res.status(500).json({ error: 'Interner Fehler', code: 500 });
|
res.status(500).json({ error: 'Interner Fehler', code: 500 });
|
||||||
@@ -267,52 +395,13 @@ router.get('/:id', (req, res) => {
|
|||||||
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(id);
|
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(id);
|
||||||
if (!contact) return res.status(404).json({ error: 'Kontakt nicht gefunden', code: 404 });
|
if (!contact) return res.status(404).json({ error: 'Kontakt nicht gefunden', code: 404 });
|
||||||
|
|
||||||
// Query multi-value fields
|
// Load multi-value fields
|
||||||
const phones = db.get().prepare(`
|
const multiValueFields = loadMultiValueFields(id);
|
||||||
SELECT id, label, value, is_primary FROM contact_phones
|
|
||||||
WHERE contact_id = ?
|
|
||||||
ORDER BY is_primary DESC, id ASC
|
|
||||||
`).all(id).map(p => ({
|
|
||||||
id: p.id,
|
|
||||||
label: p.label,
|
|
||||||
value: p.value,
|
|
||||||
isPrimary: p.is_primary === 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
const emails = db.get().prepare(`
|
|
||||||
SELECT id, label, value, is_primary FROM contact_emails
|
|
||||||
WHERE contact_id = ?
|
|
||||||
ORDER BY is_primary DESC, id ASC
|
|
||||||
`).all(id).map(e => ({
|
|
||||||
id: e.id,
|
|
||||||
label: e.label,
|
|
||||||
value: e.value,
|
|
||||||
isPrimary: e.is_primary === 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
const addresses = db.get().prepare(`
|
|
||||||
SELECT id, label, street, city, state, postal_code, country, is_primary
|
|
||||||
FROM contact_addresses
|
|
||||||
WHERE contact_id = ?
|
|
||||||
ORDER BY is_primary DESC, id ASC
|
|
||||||
`).all(id).map(a => ({
|
|
||||||
id: a.id,
|
|
||||||
label: a.label,
|
|
||||||
street: a.street,
|
|
||||||
city: a.city,
|
|
||||||
state: a.state,
|
|
||||||
postalCode: a.postal_code,
|
|
||||||
country: a.country,
|
|
||||||
isPrimary: a.is_primary === 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Combine contact with multi-value fields
|
|
||||||
res.json({
|
res.json({
|
||||||
data: {
|
data: {
|
||||||
...contact,
|
...contact,
|
||||||
phones,
|
...multiValueFields
|
||||||
emails,
|
|
||||||
addresses
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+148
@@ -2297,4 +2297,152 @@ describe('Contacts API - Multi-Value Fields', () => {
|
|||||||
assert.strictEqual(contact.addresses.length, 0, 'addresses should be empty');
|
assert.strictEqual(contact.addresses.length, 0, 'addresses should be empty');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('POST /contacts', () => {
|
||||||
|
it('should create contact with multi-value fields', async () => {
|
||||||
|
const contactsRouter = await import('./server/routes/contacts.js');
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
params: {},
|
||||||
|
query: {},
|
||||||
|
body: {
|
||||||
|
name: 'Dr. Schmidt',
|
||||||
|
category: 'Arzt',
|
||||||
|
notes: 'Hausarzt',
|
||||||
|
phones: [
|
||||||
|
{ label: 'Praxis', value: '+4930123456', isPrimary: true },
|
||||||
|
{ label: 'Mobil', value: '+491701234567', isPrimary: false }
|
||||||
|
],
|
||||||
|
emails: [
|
||||||
|
{ label: 'Praxis', value: 'praxis@schmidt.de', isPrimary: true }
|
||||||
|
],
|
||||||
|
addresses: [
|
||||||
|
{
|
||||||
|
label: 'Praxis',
|
||||||
|
street: 'Hauptstraße 10',
|
||||||
|
city: 'Berlin',
|
||||||
|
postalCode: '10115',
|
||||||
|
country: 'Deutschland',
|
||||||
|
isPrimary: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
status(code) { this.statusCode = code; return this; },
|
||||||
|
json(data) { this.data = data; return this; },
|
||||||
|
};
|
||||||
|
|
||||||
|
const postHandler = contactsRouter.default.stack.find(
|
||||||
|
layer => layer.route?.path === '/' && layer.route.methods.post
|
||||||
|
)?.route?.stack[0]?.handle;
|
||||||
|
|
||||||
|
assert.ok(postHandler, 'POST /contacts handler should exist');
|
||||||
|
await postHandler(req, res);
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
assert.strictEqual(res.statusCode, 201);
|
||||||
|
assert.ok(res.data.data, 'Response should have data field');
|
||||||
|
|
||||||
|
const contact = res.data.data;
|
||||||
|
assert.strictEqual(contact.name, 'Dr. Schmidt');
|
||||||
|
assert.strictEqual(contact.category, 'Arzt');
|
||||||
|
|
||||||
|
// Verify multi-value fields were created
|
||||||
|
assert.ok(Array.isArray(contact.phones), 'phones should be in response');
|
||||||
|
assert.strictEqual(contact.phones.length, 2);
|
||||||
|
|
||||||
|
const praxisPhone = contact.phones.find(p => p.label === 'Praxis');
|
||||||
|
assert.ok(praxisPhone, 'Should have Praxis phone');
|
||||||
|
assert.strictEqual(praxisPhone.value, '+4930123456');
|
||||||
|
assert.strictEqual(praxisPhone.isPrimary, true);
|
||||||
|
|
||||||
|
assert.ok(Array.isArray(contact.emails), 'emails should be in response');
|
||||||
|
assert.strictEqual(contact.emails.length, 1);
|
||||||
|
assert.strictEqual(contact.emails[0].value, 'praxis@schmidt.de');
|
||||||
|
|
||||||
|
assert.ok(Array.isArray(contact.addresses), 'addresses should be in response');
|
||||||
|
assert.strictEqual(contact.addresses.length, 1);
|
||||||
|
assert.strictEqual(contact.addresses[0].street, 'Hauptstraße 10');
|
||||||
|
assert.strictEqual(contact.addresses[0].city, 'Berlin');
|
||||||
|
|
||||||
|
// Verify data persisted in database
|
||||||
|
const contactId = contact.id;
|
||||||
|
const dbPhones = contactsApiDb.prepare('SELECT * FROM contact_phones WHERE contact_id = ?').all(contactId);
|
||||||
|
assert.strictEqual(dbPhones.length, 2, 'Should have 2 phones in DB');
|
||||||
|
|
||||||
|
const dbEmails = contactsApiDb.prepare('SELECT * FROM contact_emails WHERE contact_id = ?').all(contactId);
|
||||||
|
assert.strictEqual(dbEmails.length, 1, 'Should have 1 email in DB');
|
||||||
|
|
||||||
|
const dbAddresses = contactsApiDb.prepare('SELECT * FROM contact_addresses WHERE contact_id = ?').all(contactId);
|
||||||
|
assert.strictEqual(dbAddresses.length, 1, 'Should have 1 address in DB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate phones array and return 400 on invalid data', async () => {
|
||||||
|
const contactsRouter = await import('./server/routes/contacts.js');
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
params: {},
|
||||||
|
query: {},
|
||||||
|
body: {
|
||||||
|
name: 'Test Contact',
|
||||||
|
phones: [
|
||||||
|
{ label: 'Invalid' } // missing value
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
status(code) { this.statusCode = code; return this; },
|
||||||
|
json(data) { this.data = data; return this; },
|
||||||
|
};
|
||||||
|
|
||||||
|
const postHandler = contactsRouter.default.stack.find(
|
||||||
|
layer => layer.route?.path === '/' && layer.route.methods.post
|
||||||
|
)?.route?.stack[0]?.handle;
|
||||||
|
|
||||||
|
await postHandler(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.statusCode, 400);
|
||||||
|
assert.ok(res.data.error, 'Should have error message');
|
||||||
|
assert.ok(res.data.error.includes('Phone'), 'Error should mention Phone');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create contact without multi-value fields (backwards compatible)', async () => {
|
||||||
|
const contactsRouter = await import('./server/routes/contacts.js');
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
params: {},
|
||||||
|
query: {},
|
||||||
|
body: {
|
||||||
|
name: 'Simple Contact',
|
||||||
|
category: 'Sonstiges'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
status(code) { this.statusCode = code; return this; },
|
||||||
|
json(data) { this.data = data; return this; },
|
||||||
|
};
|
||||||
|
|
||||||
|
const postHandler = contactsRouter.default.stack.find(
|
||||||
|
layer => layer.route?.path === '/' && layer.route.methods.post
|
||||||
|
)?.route?.stack[0]?.handle;
|
||||||
|
|
||||||
|
await postHandler(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.statusCode, 201);
|
||||||
|
const contact = res.data.data;
|
||||||
|
assert.strictEqual(contact.name, 'Simple Contact');
|
||||||
|
|
||||||
|
// Should have empty arrays
|
||||||
|
assert.ok(Array.isArray(contact.phones));
|
||||||
|
assert.strictEqual(contact.phones.length, 0);
|
||||||
|
assert.ok(Array.isArray(contact.emails));
|
||||||
|
assert.strictEqual(contact.emails.length, 0);
|
||||||
|
assert.ok(Array.isArray(contact.addresses));
|
||||||
|
assert.strictEqual(contact.addresses.length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user