diff --git a/src/course-home/data/pact-tests/lmsPact.test.jsx b/src/course-home/data/pact-tests/lmsPact.test.jsx new file mode 100644 index 00000000..28a6efe7 --- /dev/null +++ b/src/course-home/data/pact-tests/lmsPact.test.jsx @@ -0,0 +1,222 @@ +import { Pact, Matchers } from '@pact-foundation/pact'; +import path from 'path'; +import { mergeConfig, getConfig } from '@edx/frontend-platform'; + +import { + getCourseHomeCourseMetadata, + getDatesTabData, +} from '../api'; + +import { initializeMockApp } from '../../../setupTest'; +import { + courseId, dateRegex, opaqueKeysRegex, dateTypeRegex, +} from '../../../pacts/constants'; + +const { + somethingLike: like, term, boolean, string, eachLike, +} = Matchers; +const provider = new Pact({ + consumer: 'frontend-app-learning', + provider: 'lms', + log: path.resolve(process.cwd(), 'src/course-home/data/pact-tests/logs', 'pact.log'), + dir: path.resolve(process.cwd(), 'src/pacts'), + pactfileWriteMode: 'merge', + logLevel: 'DEBUG', + cors: true, +}); + +describe('Course Home Service', () => { + beforeAll(async () => { + initializeMockApp(); + await provider + .setup() + .then((options) => mergeConfig({ + LMS_BASE_URL: `http://localhost:${options.port}`, + }, 'Custom app config for pact tests')); + }); + + afterEach(() => provider.verify()); + afterAll(() => provider.finalize()); + describe('When a request to fetch tab is made', () => { + it('returns tab data for a course_id', async () => { + await provider.addInteraction({ + state: `Tab data exists for course_id ${courseId}`, + uponReceiving: 'a request to fetch tab', + withRequest: { + method: 'GET', + path: `/api/course_home/v1/course_metadata/${courseId}`, + }, + willRespondWith: { + status: 200, + body: { + can_show_upgrade_sock: boolean(false), + verified_mode: like({ + access_expiration_date: null, + currency: 'USD', + currency_symbol: '$', + price: 149, + sku: '8CF08E5', + upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + }), + can_load_courseware: boolean(true), + celebrations: like({ + first_section: false, + streak_length_to_celebrate: null, + streak_discount_experiment_enabled: false, + }), + course_access: { + has_access: boolean(true), + error_code: null, + developer_message: null, + user_message: null, + additional_context_user_message: null, + user_fragment: null, + }, + course_id: term({ + generate: 'course-v1:edX+DemoX+Demo_Course', + matcher: opaqueKeysRegex, + }), + is_enrolled: boolean(true), + is_self_paced: boolean(false), + is_staff: boolean(true), + number: string('DemoX'), + org: string('edX'), + original_user_is_staff: boolean(true), + start: term({ + generate: '2013-02-05T05:00:00Z', + matcher: dateRegex, + }), + tabs: eachLike({ + tab_id: 'courseware', + title: 'Course', + url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`, + }), + title: string('Demonstration Course'), + username: string('edx'), + }, + }, + }); + const normalizedTabData = { + canShowUpgradeSock: false, + verifiedMode: { + accessExpirationDate: null, + currency: 'USD', + currencySymbol: '$', + price: 149, + sku: '8CF08E5', + upgradeUrl: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + }, + canLoadCourseware: true, + celebrations: { + firstSection: false, + streakLengthToCelebrate: null, + streakDiscountExperimentEnabled: false, + }, + courseAccess: { + hasAccess: true, + errorCode: null, + developerMessage: null, + userMessage: null, + additionalContextUserMessage: null, + userFragment: null, + }, + courseId: 'course-v1:edX+DemoX+Demo_Course', + isEnrolled: true, + isSelfPaced: false, + isStaff: true, + number: 'DemoX', + org: 'edX', + originalUserIsStaff: true, + start: '2013-02-05T05:00:00Z', + tabs: [ + { + slug: 'outline', + title: 'Course', + url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`, + }, + ], + title: 'Demonstration Course', + username: 'edx', + }; + const response = await getCourseHomeCourseMetadata(courseId); + expect(response).toBeTruthy(); + expect(response).toEqual(normalizedTabData); + }); + }); + + describe('When a request to fetch dates tab is made', () => { + it('returns course date blocks for a course_id', async () => { + await provider.addInteraction({ + state: `course date blocks exist for course_id ${courseId}`, + uponReceiving: 'a request to fetch dates tab', + withRequest: { + method: 'GET', + path: `/api/course_home/v1/dates/${courseId}`, + }, + willRespondWith: { + status: 200, + body: { + dates_banner_info: like({ + missed_deadlines: false, + content_type_gating_enabled: false, + missed_gated_content: false, + verified_upgrade_link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + }), + course_date_blocks: eachLike({ + assignment_type: null, + complete: null, + date: term({ + generate: '2013-02-05T05:00:00Z', + matcher: dateRegex, + }), + date_type: term({ + generate: 'verified-upgrade-deadline', + matcher: dateTypeRegex, + }), + description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.', + learner_has_access: true, + link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + link_text: 'Upgrade to Verified Certificate', + title: 'Verification Upgrade Deadline', + extra_info: null, + first_component_block_id: '', + }), + has_ended: boolean(false), + learner_is_full_access: boolean(true), + user_timezone: null, + }, + }, + }); + const camelCaseResponse = { + datesBannerInfo: { + missedDeadlines: false, + contentTypeGatingEnabled: false, + missedGatedContent: false, + verifiedUpgradeLink: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + }, + courseDateBlocks: [ + { + assignmentType: null, + complete: null, + date: '2013-02-05T05:00:00Z', + dateType: 'verified-upgrade-deadline', + description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.', + learnerHasAccess: true, + link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, + linkText: 'Upgrade to Verified Certificate', + title: 'Verification Upgrade Deadline', + extraInfo: null, + firstComponentBlockId: '', + }, + ], + hasEnded: false, + learnerIsFullAccess: true, + userTimezone: null, + }; + + const response = await getDatesTabData(courseId); + expect(response).toBeTruthy(); + expect(response).toEqual(camelCaseResponse); + }); + }); +}); diff --git a/src/courseware/data/pact-tests/lmsPact.test.jsx b/src/courseware/data/pact-tests/lmsPact.test.jsx index e856f360..b50e0e67 100644 --- a/src/courseware/data/pact-tests/lmsPact.test.jsx +++ b/src/courseware/data/pact-tests/lmsPact.test.jsx @@ -4,9 +4,19 @@ import { mergeConfig, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { - getCourseBlocks, getCourseMetadata, getSequenceMetadata, postSequencePosition, getBlockCompletion, + getCourseBlocks, + getCourseMetadata, + getSequenceMetadata, + postSequencePosition, + getBlockCompletion, + getResumeBlock, + sendActivationEmail, } from '../api'; + import { initializeMockApp } from '../../../setupTest'; +import { + courseId, dateRegex, opaqueKeysRegex, sequenceId, usageId, +} from '../../../pacts/constants'; const { somethingLike: like, term, boolean, string, eachLike, integer, @@ -15,17 +25,13 @@ const provider = new Pact({ consumer: 'frontend-app-learning', provider: 'lms', log: path.resolve(process.cwd(), 'src/courseware/data/pact-tests/logs', 'pact.log'), - dir: path.resolve(process.cwd(), 'src/courseware/data/pact-tests'), + dir: path.resolve(process.cwd(), 'src/pacts'), + pactfileWriteMode: 'merge', logLevel: 'DEBUG', cors: true, }); describe('Courseware Service', () => { - const courseId = 'course-v1:edX+DemoX+Demo_Course'; - const sequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions'; - const usageId = 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@47dbd5f836544e61877a483c0b75606c'; - const dateRegex = '^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-][01]\\d:[0-5]\\d)$'; - const opaqueKeysRegex = '[\\w\\-~.:]'; let authenticatedUser; beforeAll(async () => { initializeMockApp(); @@ -57,8 +63,7 @@ describe('Courseware Service', () => { }, willRespondWith: { status: 200, - body: - { + body: { root: string('block-v1:edX+DemoX+Demo_Course+type@course+block@course'), blocks: like({ 'block-v1:edX+DemoX+Demo_Course+type@course+block@course': { @@ -105,8 +110,7 @@ describe('Courseware Service', () => { }, willRespondWith: { status: 200, - body: - { + body: { access_expiration: { expiration_date: term({ generate: '2013-02-05T05:00:00Z', @@ -170,7 +174,10 @@ describe('Courseware Service', () => { }), user_timezone: null, verified_mode: like({ - access_expiration_date: null, + access_expiration_date: term({ + generate: '2013-02-05T05:00:00Z', + matcher: dateRegex, + }), currency: 'USD', currency_symbol: '$', price: 149, @@ -182,7 +189,7 @@ describe('Courseware Service', () => { can_view_legacy_courseware: boolean(true), is_staff: boolean(true), course_access: like({ - has_access: boolean(true), + has_access: true, error_code: null, developer_message: null, user_message: null, @@ -192,7 +199,7 @@ describe('Courseware Service', () => { notes: { enabled: boolean(false), visible: boolean(true) }, marketing_url: null, celebrations: { - irst_section: boolean(false), + first_section: boolean(false), streak_length_to_celebrate: null, streak_discount_experiment_enabled: boolean(false), }, @@ -251,28 +258,26 @@ describe('Courseware Service', () => { isStaff: true, license: 'all-rights-reserved', verifiedMode: { - accessExpirationDate: null, + accessExpirationDate: '2013-02-05T05:00:00Z', currency: 'USD', currencySymbol: '$', price: 149, sku: '8CF08E5', upgradeUrl: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`, }, - tabs: [ - { - title: 'Course', - slug: 'courseware', - priority: 0, - type: 'courseware', - url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`, - }, - ], + tabs: [{ + title: 'Course', + slug: 'courseware', + priority: 0, + type: 'courseware', + url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`, + }], userTimezone: null, showCalculator: false, notes: { enabled: false, visible: true }, marketingUrl: null, celebrations: { - irstSection: false, + firstSection: false, streakLengthToCelebrate: null, streakDiscountExperimentEnabled: false, }, @@ -311,8 +316,7 @@ describe('Courseware Service', () => { }, willRespondWith: { status: 200, - body: - { + body: { items: eachLike({ content: '', page_title: 'Pointing on a Picture', @@ -368,18 +372,16 @@ describe('Courseware Service', () => { showCompletion: false, allowProctoringOptOut: undefined, }, - units: [ - { - id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7', - sequenceId: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions', - bookmarked: false, - complete: undefined, - title: 'Pointing on a Picture', - contentType: 'problem', - graded: true, - containsContentTypeGatedContent: false, - }, - ], + units: [{ + id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7', + sequenceId: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions', + bookmarked: false, + complete: undefined, + title: 'Pointing on a Picture', + contentType: 'problem', + graded: true, + containsContentTypeGatedContent: false, + }], }; const response = await getSequenceMetadata(sequenceId); expect(response).toBeTruthy(); @@ -399,8 +401,7 @@ describe('Courseware Service', () => { }, willRespondWith: { status: 200, - body: - { + body: { success: boolean(true), }, }, @@ -423,8 +424,7 @@ describe('Courseware Service', () => { }, willRespondWith: { status: 200, - body: - { + body: { complete: boolean(true), }, }, @@ -434,4 +434,51 @@ describe('Courseware Service', () => { expect(response).toEqual(true); }); }); + + describe('When a request to get resume block is made', () => { + it('returns block id, section id and unit id of the resume block', async () => { + await provider.addInteraction({ + state: `Resume block exists for course_id ${courseId}`, + uponReceiving: 'a request to get Resume block', + withRequest: { + method: 'GET', + path: `/api/courseware/resume/${courseId}`, + }, + willRespondWith: { + status: 200, + body: { + block_id: string('642fadf46d074aabb637f20af320fb31'), + section_id: string('642fadf46d074aabb637f20af320fb87'), + unit_id: string('642fadf46d074aabb637f20af320fb99'), + }, + }, + }); + const camelCaseResponse = { + blockId: '642fadf46d074aabb637f20af320fb31', + sectionId: '642fadf46d074aabb637f20af320fb87', + unitId: '642fadf46d074aabb637f20af320fb99', + }; + const response = await getResumeBlock(courseId); + expect(response).toBeTruthy(); + expect(response).toEqual(camelCaseResponse); + }); + }); + + describe('When a request to send activation email is made', () => { + it('returns status code 200', async () => { + await provider.addInteraction({ + state: 'A logged-in user may or may not be active', + uponReceiving: 'a request to send activation email', + withRequest: { + method: 'POST', + path: '/api/send_account_activation_email', + }, + willRespondWith: { + status: 200, + }, + }); + const response = await sendActivationEmail(); + expect(response).toEqual(''); + }); + }); }); diff --git a/src/pacts/constants.js b/src/pacts/constants.js new file mode 100644 index 00000000..41d89727 --- /dev/null +++ b/src/pacts/constants.js @@ -0,0 +1,6 @@ +export const courseId = 'course-v1:edX+DemoX+Demo_Course'; +export const dateRegex = '^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-][01]\\d:[0-5]\\d)$'; +export const opaqueKeysRegex = '[\\w\\-~.:]'; +export const sequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions'; +export const usageId = 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@47dbd5f836544e61877a483c0b75606c'; +export const dateTypeRegex = '^(event|todays-date|course-start-date|course-end-date|assignment-due-date|course-expired-date|certificate-available-date|verified-upgrade-deadline|verification-deadline-date)$'; diff --git a/src/courseware/data/pact-tests/frontend-app-learning-lms.json b/src/pacts/frontend-app-learning-lms.json similarity index 66% rename from src/courseware/data/pact-tests/frontend-app-learning-lms.json rename to src/pacts/frontend-app-learning-lms.json index 9ed36fa6..e176d55c 100644 --- a/src/courseware/data/pact-tests/frontend-app-learning-lms.json +++ b/src/pacts/frontend-app-learning-lms.json @@ -6,6 +6,180 @@ "name": "lms" }, "interactions": [ + { + "description": "a request to fetch tab", + "providerState": "Tab data exists for course_id course-v1:edX+DemoX+Demo_Course", + "request": { + "method": "GET", + "path": "/api/course_home/v1/course_metadata/course-v1:edX+DemoX+Demo_Course" + }, + "response": { + "status": 200, + "headers": { + }, + "body": { + "can_show_upgrade_sock": false, + "verified_mode": { + "access_expiration_date": null, + "currency": "USD", + "currency_symbol": "$", + "price": 149, + "sku": "8CF08E5", + "upgrade_url": "http://localhost:18130/basket/add/?sku=8CF08E5" + }, + "can_load_courseware": true, + "celebrations": { + "first_section": false, + "streak_length_to_celebrate": null, + "streak_discount_experiment_enabled": false + }, + "course_access": { + "has_access": true, + "error_code": null, + "developer_message": null, + "user_message": null, + "additional_context_user_message": null, + "user_fragment": null + }, + "course_id": "course-v1:edX+DemoX+Demo_Course", + "is_enrolled": true, + "is_self_paced": false, + "is_staff": true, + "number": "DemoX", + "org": "edX", + "original_user_is_staff": true, + "start": "2013-02-05T05:00:00Z", + "tabs": [ + { + "tab_id": "courseware", + "title": "Course", + "url": "http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course/home" + } + ], + "title": "Demonstration Course", + "username": "edx" + }, + "matchingRules": { + "$.body.can_show_upgrade_sock": { + "match": "type" + }, + "$.body.verified_mode": { + "match": "type" + }, + "$.body.can_load_courseware": { + "match": "type" + }, + "$.body.celebrations": { + "match": "type" + }, + "$.body.course_access.has_access": { + "match": "type" + }, + "$.body.course_id": { + "match": "regex", + "regex": "[\\w\\-~.:]" + }, + "$.body.is_enrolled": { + "match": "type" + }, + "$.body.is_self_paced": { + "match": "type" + }, + "$.body.is_staff": { + "match": "type" + }, + "$.body.number": { + "match": "type" + }, + "$.body.org": { + "match": "type" + }, + "$.body.original_user_is_staff": { + "match": "type" + }, + "$.body.start": { + "match": "regex", + "regex": "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-][01]\\d:[0-5]\\d)$" + }, + "$.body.tabs": { + "min": 1 + }, + "$.body.tabs[*].*": { + "match": "type" + }, + "$.body.title": { + "match": "type" + }, + "$.body.username": { + "match": "type" + } + } + } + }, + { + "description": "a request to fetch dates tab", + "providerState": "course date blocks exist for course_id course-v1:edX+DemoX+Demo_Course", + "request": { + "method": "GET", + "path": "/api/course_home/v1/dates/course-v1:edX+DemoX+Demo_Course" + }, + "response": { + "status": 200, + "headers": { + }, + "body": { + "dates_banner_info": { + "missed_deadlines": false, + "content_type_gating_enabled": false, + "missed_gated_content": false, + "verified_upgrade_link": "http://localhost:18130/basket/add/?sku=8CF08E5" + }, + "course_date_blocks": [ + { + "assignment_type": null, + "complete": null, + "date": "2013-02-05T05:00:00Z", + "date_type": "verified-upgrade-deadline", + "description": "You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.", + "learner_has_access": true, + "link": "http://localhost:18130/basket/add/?sku=8CF08E5", + "link_text": "Upgrade to Verified Certificate", + "title": "Verification Upgrade Deadline", + "extra_info": null, + "first_component_block_id": "" + } + ], + "has_ended": false, + "learner_is_full_access": true, + "user_timezone": null + }, + "matchingRules": { + "$.body.dates_banner_info": { + "match": "type" + }, + "$.body.course_date_blocks": { + "min": 1 + }, + "$.body.course_date_blocks[*].*": { + "match": "type" + }, + "$.body.course_date_blocks[*].date": { + "match": "regex", + "regex": "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-][01]\\d:[0-5]\\d)$" + }, + "$.body.course_date_blocks[*].date_type": { + "match": "regex", + "regex": "^(event|todays-date|course-start-date|course-end-date|assignment-due-date|course-expired-date|certificate-available-date|verified-upgrade-deadline|verification-deadline-date)$" + }, + "$.body.has_ended": { + "match": "type" + }, + "$.body.learner_is_full_access": { + "match": "type" + } + } + } + }, { "description": "a request to get course blocks", "providerState": "Blocks data exists for course_id course-v1:edX+DemoX+Demo_Course", @@ -97,7 +271,7 @@ ], "user_timezone": null, "verified_mode": { - "access_expiration_date": null, + "access_expiration_date": "2013-02-05T05:00:00Z", "currency": "USD", "currency_symbol": "$", "price": 149, @@ -122,7 +296,7 @@ }, "marketing_url": null, "celebrations": { - "irst_section": false, + "first_section": false, "streak_length_to_celebrate": null, "streak_discount_experiment_enabled": false }, @@ -232,6 +406,10 @@ "$.body.verified_mode": { "match": "type" }, + "$.body.verified_mode.access_expiration_date": { + "match": "regex", + "regex": "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|[+-][01]\\d:[0-5]\\d)$" + }, "$.body.show_calculator": { "match": "type" }, @@ -247,16 +425,13 @@ "$.body.course_access": { "match": "type" }, - "$.body.course_access.has_access": { - "match": "type" - }, "$.body.notes.enabled": { "match": "type" }, "$.body.notes.visible": { "match": "type" }, - "$.body.celebrations.irst_section": { + "$.body.celebrations.first_section": { "match": "type" }, "$.body.celebrations.streak_discount_experiment_enabled": { @@ -413,6 +588,48 @@ } } } + }, + { + "description": "a request to get Resume block", + "providerState": "Resume block exists for course_id course-v1:edX+DemoX+Demo_Course", + "request": { + "method": "GET", + "path": "/api/courseware/resume/course-v1:edX+DemoX+Demo_Course" + }, + "response": { + "status": 200, + "headers": { + }, + "body": { + "block_id": "642fadf46d074aabb637f20af320fb31", + "section_id": "642fadf46d074aabb637f20af320fb87", + "unit_id": "642fadf46d074aabb637f20af320fb99" + }, + "matchingRules": { + "$.body.block_id": { + "match": "type" + }, + "$.body.section_id": { + "match": "type" + }, + "$.body.unit_id": { + "match": "type" + } + } + } + }, + { + "description": "a request to send activation email", + "providerState": "A logged-in user may or may not be active", + "request": { + "method": "POST", + "path": "/api/send_account_activation_email" + }, + "response": { + "status": 200, + "headers": { + } + } } ], "metadata": {