Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion scenarios/user-journey/form-lifecycle/05-form-publishing.http
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,36 @@ GET {{BASE_URL}}/forms/{{formId}}

###

# ============================================
# Verify Published Form Appears in User Form List (GET /forms/me)
# ============================================
# @name verifyFormInUserListing
# @ref getFormAfterPublish
GET {{BASE_URL}}/forms/me

?? status == 200
{{
test.hasResponseBody();

const { equal } = require('assert');
const data = JSON.parse(response.body);

test('Published form with a future deadline appears in GET /forms/me', () => {
equal(Array.isArray(data), true, 'Response must be a bare array');
const row = data.find((r) => r.id === formId);
equal(row !== undefined, true, 'Published form should appear in user form list');
equal(row.deadline !== undefined && row.deadline !== null, true, 'Lifecycle form should have a deadline');
equal(Array.isArray(row.responseIds), true, 'responseIds must always be an array');
});
}}

###

# ============================================
# Verify Published Form Appears in Listing
# ============================================
# @name verifyFormInListing
# @ref getFormAfterPublish
# @ref verifyFormInUserListing
GET {{BASE_URL}}/forms

{{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# ============================================
# GET /forms/me — deadline filter (CORE-371)
# ============================================
# Backend excludes expired published forms from the user list while keeping forms
# with no deadline. This file publishes two auxiliary forms and asserts listing behavior.

# @import ./05-form-publishing.http

###

# ============================================
# Create published form with no deadline
# ============================================
# @name createNoDeadlineForm
# @ref getFormFinalState
POST {{BASE_URL}}/orgs/{{orgSlug}}/forms
Content-Type: application/json

{
"title": "No Deadline Me-List Test Form",
"visibility": "PUBLIC"
}

?? status <= 201
{{
const data = JSON.parse(response.body);
exports.noDeadlineFormId = data.id;
}}

###

# @name linkNoDeadlineWorkflow
# @ref createNoDeadlineForm
GET {{BASE_URL}}/forms/{{noDeadlineFormId}}/workflow

{{
const { equal } = require('assert');
const data = JSON.parse(response.body);
const start = data.workflow.find((n) => n.type === 'START' || n.type === 'start');
const end = data.workflow.find((n) => n.type === 'END' || n.type === 'end');
equal(start !== undefined, true, 'START node should exist');
equal(end !== undefined, true, 'END node should exist');
exports.ndStartId = start.id;
exports.ndEndId = end.id;
}}

###

# @name createNoDeadlineSectionNode
# @ref linkNoDeadlineWorkflow
POST {{BASE_URL}}/forms/{{noDeadlineFormId}}/workflow/nodes
Content-Type: application/json

{
"type": "SECTION",
"payload": { "x": 1.0, "y": 2.0 }
}

?? status == 201
{{
exports.ndSectionNodeId = JSON.parse(response.body).id;
exports.ndSectionNodeLabel = JSON.parse(response.body).label;
}}

###

# @name activateNoDeadlineWorkflow
# @ref createNoDeadlineSectionNode
PUT {{BASE_URL}}/forms/{{noDeadlineFormId}}/workflow
Content-Type: application/json

[
{ "id": "{{ndStartId}}", "label": "開始表單", "payload": { "x": 0, "y": 0 }, "next": "{{ndSectionNodeId}}" },
{ "id": "{{ndSectionNodeId}}", "label": "{{ndSectionNodeLabel}}", "payload": { "x": 1, "y": 2 }, "next": "{{ndEndId}}" },
{ "id": "{{ndEndId}}", "label": "確認/送出", "payload": { "x": 0, "y": 1 } }
]

?? status == 200

###

# @name addNoDeadlineQuestion
# @ref activateNoDeadlineWorkflow
GET {{BASE_URL}}/forms/{{noDeadlineFormId}}/sections

{{
exports.ndSectionId = JSON.parse(response.body)[0].section.id;
}}

###

# @name publishNoDeadlineForm
# @ref addNoDeadlineQuestion
POST {{BASE_URL}}/sections/{{ndSectionId}}/questions
Content-Type: application/json

{
"required": true,
"type": "SHORT_TEXT",
"title": "Name",
"order": 1
}

?? status == 201

###

# @name publishNoDeadlineFormDone
# @ref publishNoDeadlineForm
POST {{BASE_URL}}/forms/{{noDeadlineFormId}}/publish

?? status == 200

###

# ============================================
# Create published form with an expired deadline
# ============================================
# @name createExpiredForm
# @ref publishNoDeadlineFormDone
POST {{BASE_URL}}/orgs/{{orgSlug}}/forms
Content-Type: application/json

{
"title": "Expired Me-List Test Form",
"visibility": "PUBLIC"
}

?? status <= 201
{{
exports.expiredFormId = JSON.parse(response.body).id;
}}

###

# @name setExpiredDeadline
# @ref createExpiredForm
PATCH {{BASE_URL}}/forms/{{expiredFormId}}
Content-Type: application/json

{
"deadline": "2020-01-01T00:00:00Z"
}

?? status == 200

###

# @name linkExpiredWorkflow
# @ref setExpiredDeadline
GET {{BASE_URL}}/forms/{{expiredFormId}}/workflow

{{
const data = JSON.parse(response.body);
exports.exStartId = data.workflow.find((n) => n.type === 'START' || n.type === 'start').id;
exports.exEndId = data.workflow.find((n) => n.type === 'END' || n.type === 'end').id;
}}

###

# @name createExpiredSectionNode
# @ref linkExpiredWorkflow
POST {{BASE_URL}}/forms/{{expiredFormId}}/workflow/nodes
Content-Type: application/json

{
"type": "SECTION",
"payload": { "x": 1.0, "y": 2.0 }
}

?? status == 201
{{
const data = JSON.parse(response.body);
exports.exSectionNodeId = data.id;
exports.exSectionNodeLabel = data.label;
}}

###

# @name activateExpiredWorkflow
# @ref createExpiredSectionNode
PUT {{BASE_URL}}/forms/{{expiredFormId}}/workflow
Content-Type: application/json

[
{ "id": "{{exStartId}}", "label": "開始表單", "payload": { "x": 0, "y": 0 }, "next": "{{exSectionNodeId}}" },
{ "id": "{{exSectionNodeId}}", "label": "{{exSectionNodeLabel}}", "payload": { "x": 1, "y": 2 }, "next": "{{exEndId}}" },
{ "id": "{{exEndId}}", "label": "確認/送出", "payload": { "x": 0, "y": 1 } }
]

?? status == 200

###

# @name addExpiredQuestion
# @ref activateExpiredWorkflow
GET {{BASE_URL}}/forms/{{expiredFormId}}/sections

{{
exports.exSectionId = JSON.parse(response.body)[0].section.id;
}}

###

# @name publishExpiredForm
# @ref addExpiredQuestion
POST {{BASE_URL}}/sections/{{exSectionId}}/questions
Content-Type: application/json

{
"required": true,
"type": "SHORT_TEXT",
"title": "Name",
"order": 1
}

?? status == 201

###

# @name publishExpiredFormDone
# @ref publishExpiredForm
POST {{BASE_URL}}/forms/{{expiredFormId}}/publish

?? status == 200

###

# ============================================
# GET /forms/me — null-deadline visible, expired hidden
# ============================================
# @name verifyFormsMeDeadlineFilter
# @ref publishExpiredFormDone
GET {{BASE_URL}}/forms/me

?? status == 200
{{
test.hasResponseBody();

const { equal } = require('assert');
const data = JSON.parse(response.body);

test('GET /forms/me includes lifecycle and no-deadline forms, excludes expired', () => {
equal(Array.isArray(data), true, 'Response must be a bare array');

const lifecycle = data.find((r) => r.id === formId);
const noDeadline = data.find((r) => r.id === noDeadlineFormId);
const expired = data.find((r) => r.id === expiredFormId);

equal(lifecycle !== undefined, true, 'Published lifecycle form should remain listed');
equal(noDeadline !== undefined, true, 'Published form without a deadline should remain listed');
equal(noDeadline.deadline === null || noDeadline.deadline === undefined, true, 'No-deadline form should omit or null deadline');
equal(expired === undefined, true, 'Published form with a past deadline should be excluded');
});
}}
5 changes: 5 additions & 0 deletions scenarios/user-journey/form-lifecycle/journey.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
name: "Form Lifecycle"
description: "Positive end-to-end coverage for the form lifecycle, including highlight management and archive recovery."
cases:
- name: "Forms Me Deadline Filter"
description: "Verify GET /forms/me keeps null-deadline published forms and excludes expired ones (CORE-371)."
path: "05a-forms-me-deadline-filter.http"
test: "verifyFormsMeDeadlineFilter"

- name: "Highlight Lifecycle"
description: "Verify highlight configuration, statistics, title updates, and cleanup on a published form."
path: "06a-form-highlight.http"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
# with only RANKING. The backend resolves the Ranking choices from the
# stored DMC answer (DB fallback path).
#
# Each scenario uses its own independent response so the tests are isolated.
# Each scenario uses its own response on the same form. Scenario B runs after
# deleting scenario A's response so only one draft exists per user at a time.

# Import form creation from Phase 2
# @import ./02-form-creation.http
Expand Down Expand Up @@ -172,6 +173,16 @@ Content-Type: application/json
});
}}

###
# ============================================
# Delete response A so scenario B can create a fresh draft (one response per user)
# ============================================
# @name deleteResponseA
# @ref submitBatchA
DELETE {{BASE_URL}}/forms/{{formId}}/responses/{{responseIdA}}

?? status == 204

###
# ============================================
# SCENARIO B: Two-step submission
Expand All @@ -181,7 +192,7 @@ Content-Type: application/json
# [B] Create Response
# ============================================
# @name createResponseB
# @ref verifyFormPublished
# @ref deleteResponseA
POST {{BASE_URL}}/forms/{{formId}}/responses

{{
Expand All @@ -202,7 +213,7 @@ POST {{BASE_URL}}/forms/{{formId}}/responses
###

# ============================================
# GET /forms/me after response B — responseIds lists both responses for this form
# GET /forms/me after response B — responseIds tracks the new draft only
# ============================================
# @name listUserFormsAfterResponseB
# @ref createResponseB
Expand All @@ -215,14 +226,14 @@ GET {{BASE_URL}}/forms/me
const { equal } = require('assert');
const data = JSON.parse(response.body);

test('GET /forms/me includes both response ids for this form', () => {
test('GET /forms/me includes only response B after scenario A was deleted', () => {
equal(Array.isArray(data), true, 'Response must be a bare array');
const row = data.find((r) => r.id === formId);
equal(row !== undefined, true, 'User form list should include this form');
equal(Array.isArray(row.responseIds), true, 'responseIds must always be an array');
equal(row.responseIds.includes(responseIdA), true, 'responseIds should include response A');
equal(row.responseIds.includes(responseIdB), true, 'responseIds should include response B');
equal(row.responseIds.length >= 2, true, 'responseIds should list at least both responses');
equal(row.responseIds.includes(responseIdA), false, 'responseIds should not include deleted response A');
equal(row.responseIds.length, 1, 'Only one response per user per form');
});
}}

Expand Down
Loading
Loading