@nexttylabs/echo 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/lib/auth/config.ts +0 -7
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Echo Team
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { test, expect, uniqueEmail, uniqueName, TestHelpers } from "./helpers/test-utils";
|
|
19
|
-
import { TestDataManager, createTestFeedback } from "./fixtures/test-data";
|
|
20
|
-
|
|
21
|
-
test.describe("E2E-UF-012: Edit feedback title and description", () => {
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
-
let slug: string;
|
|
24
|
-
let helpers: TestHelpers;
|
|
25
|
-
let testDataManager: TestDataManager;
|
|
26
|
-
let organizationId: string;
|
|
27
|
-
let feedbackId: number;
|
|
28
|
-
|
|
29
|
-
test.beforeEach(async ({ page, request }) => {
|
|
30
|
-
helpers = new TestHelpers(page, request);
|
|
31
|
-
testDataManager = new TestDataManager(page.request);
|
|
32
|
-
|
|
33
|
-
const email = uniqueEmail();
|
|
34
|
-
const name = uniqueName();
|
|
35
|
-
const password = "StrongPass123!";
|
|
36
|
-
|
|
37
|
-
slug = await helpers.registerAndLogin(name, email, password);
|
|
38
|
-
|
|
39
|
-
organizationId = helpers.getOrganizationId();
|
|
40
|
-
|
|
41
|
-
// Create test feedback
|
|
42
|
-
const feedback = await testDataManager.createFeedback({
|
|
43
|
-
...createTestFeedback({
|
|
44
|
-
title: "Original Title",
|
|
45
|
-
description: "Original description that will be edited.",
|
|
46
|
-
}),
|
|
47
|
-
organizationId,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
feedbackId = feedback.feedbackId;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("edits feedback title and description successfully", async ({ page }) => {
|
|
54
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
55
|
-
|
|
56
|
-
// Open actions menu and click edit
|
|
57
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
58
|
-
await actionsTrigger.click();
|
|
59
|
-
await page.getByRole("menuitem", { name: /编辑|edit/i }).click();
|
|
60
|
-
|
|
61
|
-
// Edit title
|
|
62
|
-
const titleField = page.getByLabel("Title");
|
|
63
|
-
await titleField.clear();
|
|
64
|
-
await titleField.fill("Updated Title");
|
|
65
|
-
|
|
66
|
-
// Edit description
|
|
67
|
-
const descriptionField = page.getByLabel("Description");
|
|
68
|
-
await descriptionField.clear();
|
|
69
|
-
await descriptionField.fill("Updated description with new information.");
|
|
70
|
-
|
|
71
|
-
// Save changes
|
|
72
|
-
await page.getByRole("button", { name: /save|update/i }).click();
|
|
73
|
-
|
|
74
|
-
await page.waitForURL(new RegExp(`/admin/feedback/${feedbackId}$`));
|
|
75
|
-
|
|
76
|
-
// Verify changes are saved
|
|
77
|
-
await expect(page.getByRole("heading", { name: "Updated Title" })).toBeVisible();
|
|
78
|
-
await expect(page.getByText("Updated description with new information.")).toBeVisible();
|
|
79
|
-
|
|
80
|
-
// Verify old content is not visible
|
|
81
|
-
await expect(page.getByText("Original Title")).not.toBeVisible();
|
|
82
|
-
await expect(page.getByText("Original description that will be edited.")).not.toBeVisible();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("persists changes after page refresh", async ({ page }) => {
|
|
86
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
87
|
-
|
|
88
|
-
// Edit feedback
|
|
89
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
90
|
-
await actionsTrigger.click();
|
|
91
|
-
await page.getByRole("menuitem", { name: /编辑|edit/i }).click();
|
|
92
|
-
|
|
93
|
-
await page.getByLabel("Title").clear();
|
|
94
|
-
await page.getByLabel("Title").fill("Persistent Title");
|
|
95
|
-
|
|
96
|
-
await page.getByLabel("Description").clear();
|
|
97
|
-
await page.getByLabel("Description").fill("This should persist after refresh.");
|
|
98
|
-
|
|
99
|
-
await page.getByRole("button", { name: /save|update/i }).click();
|
|
100
|
-
|
|
101
|
-
await page.waitForURL(new RegExp(`/admin/feedback/${feedbackId}$`));
|
|
102
|
-
|
|
103
|
-
// Refresh page
|
|
104
|
-
await page.reload();
|
|
105
|
-
|
|
106
|
-
// Changes should still be visible
|
|
107
|
-
await expect(page.getByRole("heading", { name: "Persistent Title" })).toBeVisible();
|
|
108
|
-
await expect(page.getByText("This should persist after refresh.")).toBeVisible();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("shows confirmation before discarding changes", async ({ page }) => {
|
|
112
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
113
|
-
|
|
114
|
-
// Start editing
|
|
115
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
116
|
-
await actionsTrigger.click();
|
|
117
|
-
await page.getByRole("menuitem", { name: /编辑|edit/i }).click();
|
|
118
|
-
|
|
119
|
-
// Make changes
|
|
120
|
-
await page.getByLabel("Title").fill("Changed Title");
|
|
121
|
-
|
|
122
|
-
// Try to navigate away without saving
|
|
123
|
-
await page.goto(`/admin/feedback`);
|
|
124
|
-
|
|
125
|
-
// Should show confirmation dialog
|
|
126
|
-
const confirmDialog = page.locator('[data-testid="confirm-dialog"], .modal');
|
|
127
|
-
if (await confirmDialog.isVisible()) {
|
|
128
|
-
await expect(confirmDialog.getByText(/discard.*changes|unsaved/i)).toBeVisible();
|
|
129
|
-
|
|
130
|
-
// Click cancel to stay on page
|
|
131
|
-
await page.getByRole("button", { name: /cancel/i }).click();
|
|
132
|
-
|
|
133
|
-
// Should still be on edit page
|
|
134
|
-
expect(page.url()).toContain(`/admin/feedback/${feedbackId}`);
|
|
135
|
-
|
|
136
|
-
// Save the changes
|
|
137
|
-
await page.getByRole("button", { name: /save|update/i }).click();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("validates title is not empty", async ({ page }) => {
|
|
142
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
143
|
-
|
|
144
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
145
|
-
await actionsTrigger.click();
|
|
146
|
-
await page.getByRole("menuitem", { name: /编辑|edit/i }).click();
|
|
147
|
-
|
|
148
|
-
// Clear title
|
|
149
|
-
await page.getByLabel("Title").clear();
|
|
150
|
-
|
|
151
|
-
// Try to save
|
|
152
|
-
await page.getByRole("button", { name: /save|update/i }).click();
|
|
153
|
-
|
|
154
|
-
// Should show validation error
|
|
155
|
-
await expect(page.getByText(/title.*required|required/i)).toBeVisible();
|
|
156
|
-
|
|
157
|
-
// Should not save
|
|
158
|
-
expect(page.url()).toContain(`/admin/feedback/${feedbackId}`);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("requires permission to edit feedback", async ({ page, request }) => {
|
|
162
|
-
// Create member user without edit permission
|
|
163
|
-
const memberEmail = uniqueEmail();
|
|
164
|
-
const memberName = uniqueName();
|
|
165
|
-
|
|
166
|
-
await request.post("/api/auth/register", {
|
|
167
|
-
data: {
|
|
168
|
-
name: memberName,
|
|
169
|
-
email: memberEmail,
|
|
170
|
-
password: "TestPass123!",
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Logout admin
|
|
175
|
-
await page.context().clearCookies();
|
|
176
|
-
|
|
177
|
-
// Login as member
|
|
178
|
-
await helpers.login(memberEmail, "TestPass123!");
|
|
179
|
-
|
|
180
|
-
// Try to edit feedback
|
|
181
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
182
|
-
|
|
183
|
-
// Should not see actions menu
|
|
184
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
185
|
-
await expect(actionsTrigger).not.toBeVisible();
|
|
186
|
-
|
|
187
|
-
// Try API call
|
|
188
|
-
const response = await page.request.patch(`/api/feedback/${feedbackId}`, {
|
|
189
|
-
data: { title: "Hacked Title", description: "Hacked description" }
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
expect(response.status()).toBe(403);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test.describe("E2E-UF-013: Delete feedback", () => {
|
|
197
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
198
|
-
let slug: string;
|
|
199
|
-
let helpers: TestHelpers;
|
|
200
|
-
let testDataManager: TestDataManager;
|
|
201
|
-
let organizationId: string;
|
|
202
|
-
let feedbackId: number;
|
|
203
|
-
|
|
204
|
-
test.beforeEach(async ({ page, request }) => {
|
|
205
|
-
helpers = new TestHelpers(page, request);
|
|
206
|
-
testDataManager = new TestDataManager(page.request);
|
|
207
|
-
|
|
208
|
-
const email = uniqueEmail();
|
|
209
|
-
const name = uniqueName();
|
|
210
|
-
const password = "StrongPass123!";
|
|
211
|
-
|
|
212
|
-
slug = await helpers.registerAndLogin(name, email, password);
|
|
213
|
-
|
|
214
|
-
organizationId = helpers.getOrganizationId();
|
|
215
|
-
|
|
216
|
-
// Create test feedback
|
|
217
|
-
const feedback = await testDataManager.createFeedback({
|
|
218
|
-
...createTestFeedback({
|
|
219
|
-
title: "Feedback To Delete",
|
|
220
|
-
description: "This feedback will be deleted.",
|
|
221
|
-
}),
|
|
222
|
-
organizationId,
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
feedbackId = feedback.feedbackId;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("deletes feedback with confirmation", async ({ page }) => {
|
|
229
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
230
|
-
|
|
231
|
-
// Open actions menu and click delete
|
|
232
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
233
|
-
await actionsTrigger.click();
|
|
234
|
-
await page.getByRole("menuitem", { name: /删除|delete/i }).click();
|
|
235
|
-
|
|
236
|
-
// Should show confirmation dialog
|
|
237
|
-
const confirmDialog = page.locator('[data-testid="delete-confirm"], .modal');
|
|
238
|
-
await expect(confirmDialog).toBeVisible();
|
|
239
|
-
await expect(
|
|
240
|
-
confirmDialog.getByRole("heading", { name: /确认删除|delete/i }),
|
|
241
|
-
).toBeVisible();
|
|
242
|
-
|
|
243
|
-
// Confirm deletion
|
|
244
|
-
await confirmDialog.getByRole("button", { name: /delete|confirm|删除/i }).click();
|
|
245
|
-
|
|
246
|
-
// Should redirect to feedback list
|
|
247
|
-
await page.waitForURL(/\/admin\/feedback$/);
|
|
248
|
-
|
|
249
|
-
// Verify feedback is not in list
|
|
250
|
-
await expect(page.getByText("Feedback To Delete")).not.toBeVisible();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("cancels deletion when clicking cancel", async ({ page }) => {
|
|
254
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
255
|
-
|
|
256
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
257
|
-
await actionsTrigger.click();
|
|
258
|
-
await page.getByRole("menuitem", { name: /删除|delete/i }).click();
|
|
259
|
-
|
|
260
|
-
// Click cancel in confirmation dialog
|
|
261
|
-
const confirmDialog = page.locator('[data-testid="delete-confirm"], .modal');
|
|
262
|
-
await confirmDialog.getByRole("button", { name: /cancel|取消/i }).click();
|
|
263
|
-
|
|
264
|
-
// Should stay on feedback detail page
|
|
265
|
-
expect(page.url()).toContain(`/admin/feedback/${feedbackId}`);
|
|
266
|
-
await expect(page.getByRole("heading", { name: "Feedback To Delete" })).toBeVisible();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("shows 404 when accessing deleted feedback", async ({ page }) => {
|
|
270
|
-
// Delete the feedback first
|
|
271
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
272
|
-
|
|
273
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
274
|
-
await actionsTrigger.click();
|
|
275
|
-
await page.getByRole("menuitem", { name: /删除|delete/i }).click();
|
|
276
|
-
|
|
277
|
-
const confirmDialog = page.locator('[data-testid="delete-confirm"], .modal');
|
|
278
|
-
await confirmDialog.getByRole("button", { name: /delete|confirm|删除/i }).click();
|
|
279
|
-
await page.waitForURL(/\/admin\/feedback$/);
|
|
280
|
-
|
|
281
|
-
// Try to access deleted feedback directly
|
|
282
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
283
|
-
|
|
284
|
-
// Should show 404 or not found
|
|
285
|
-
await expect(page.getByRole("heading", { name: "404" })).toBeVisible();
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("can delete from list view", async ({ page }) => {
|
|
289
|
-
await page.goto(`/admin/feedback`);
|
|
290
|
-
|
|
291
|
-
// Find feedback in list
|
|
292
|
-
const feedbackItem = page.locator('[data-testid="feedback-item"]:has-text("Feedback To Delete")');
|
|
293
|
-
await expect(feedbackItem).toBeVisible();
|
|
294
|
-
|
|
295
|
-
// Click delete button in list
|
|
296
|
-
const listDeleteButton = feedbackItem.locator('button').filter({ hasText: /delete|删除/i });
|
|
297
|
-
if (await listDeleteButton.isVisible()) {
|
|
298
|
-
await listDeleteButton.click();
|
|
299
|
-
|
|
300
|
-
// Confirm deletion
|
|
301
|
-
const confirmDialog = page.locator('[data-testid="delete-confirm"], .modal');
|
|
302
|
-
await confirmDialog.getByRole("button", { name: /delete|confirm|删除/i }).click();
|
|
303
|
-
|
|
304
|
-
// Feedback should be removed from list
|
|
305
|
-
await expect(feedbackItem).not.toBeVisible();
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test("requires permission to delete feedback", async ({ page, request }) => {
|
|
310
|
-
// Create member user
|
|
311
|
-
const memberEmail = uniqueEmail();
|
|
312
|
-
const memberName = uniqueName();
|
|
313
|
-
|
|
314
|
-
await request.post("/api/auth/register", {
|
|
315
|
-
data: {
|
|
316
|
-
name: memberName,
|
|
317
|
-
email: memberEmail,
|
|
318
|
-
password: "TestPass123!",
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Logout admin
|
|
323
|
-
await page.context().clearCookies();
|
|
324
|
-
|
|
325
|
-
// Login as member
|
|
326
|
-
await helpers.login(memberEmail, "TestPass123!");
|
|
327
|
-
|
|
328
|
-
// Try to delete feedback
|
|
329
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
330
|
-
|
|
331
|
-
// Should not see delete button
|
|
332
|
-
const actionsTrigger = page.getByTestId("feedback-actions-trigger");
|
|
333
|
-
await expect(actionsTrigger).not.toBeVisible();
|
|
334
|
-
|
|
335
|
-
// Try API call
|
|
336
|
-
const response = await page.request.delete(`/api/feedback/${feedbackId}`);
|
|
337
|
-
expect(response.status()).toBe(403);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test.describe("E2E-UF-014: Add internal notes", () => {
|
|
342
|
-
let slug: string;
|
|
343
|
-
let helpers: TestHelpers;
|
|
344
|
-
let testDataManager: TestDataManager;
|
|
345
|
-
let organizationId: string;
|
|
346
|
-
let feedbackId: number;
|
|
347
|
-
let adminName: string;
|
|
348
|
-
|
|
349
|
-
test.beforeEach(async ({ page, request }) => {
|
|
350
|
-
helpers = new TestHelpers(page, request);
|
|
351
|
-
testDataManager = new TestDataManager(page.request);
|
|
352
|
-
|
|
353
|
-
const email = uniqueEmail();
|
|
354
|
-
adminName = uniqueName();
|
|
355
|
-
const password = "StrongPass123!";
|
|
356
|
-
|
|
357
|
-
slug = await helpers.registerAndLogin(adminName, email, password);
|
|
358
|
-
|
|
359
|
-
organizationId = helpers.getOrganizationId();
|
|
360
|
-
|
|
361
|
-
// Create test feedback
|
|
362
|
-
const feedback = await testDataManager.createFeedback({
|
|
363
|
-
...createTestFeedback({
|
|
364
|
-
title: "Feedback with Notes",
|
|
365
|
-
description: "This feedback will have internal notes.",
|
|
366
|
-
}),
|
|
367
|
-
organizationId,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
feedbackId = feedback.feedbackId;
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test("adds internal note successfully", async ({ page }) => {
|
|
374
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
375
|
-
|
|
376
|
-
// Find internal notes section
|
|
377
|
-
const notesSection = page.locator('[data-testid="internal-notes"], .internal-notes');
|
|
378
|
-
await expect(notesSection).toBeVisible();
|
|
379
|
-
|
|
380
|
-
// Enter note content
|
|
381
|
-
const noteTextarea = page.getByPlaceholder(/添加内部备注|internal note/i);
|
|
382
|
-
await noteTextarea.fill("This is an internal note for the team.");
|
|
383
|
-
|
|
384
|
-
// Save note
|
|
385
|
-
await page.getByRole("button", { name: /添加备注|add/i }).click();
|
|
386
|
-
|
|
387
|
-
// Verify note is displayed
|
|
388
|
-
await expect(page.getByText("This is an internal note for the team.")).toBeVisible();
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("shows notes are only visible to team members", async ({ page }) => {
|
|
392
|
-
// Add a note first
|
|
393
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
394
|
-
|
|
395
|
-
const noteTextarea = page.getByPlaceholder(/添加内部备注|internal note/i);
|
|
396
|
-
await noteTextarea.fill("Confidential internal note.");
|
|
397
|
-
await page.getByRole("button", { name: /添加备注|add/i }).click();
|
|
398
|
-
|
|
399
|
-
// Logout and view as public
|
|
400
|
-
await page.context().clearCookies();
|
|
401
|
-
|
|
402
|
-
// View feedback via tracking URL
|
|
403
|
-
const trackingUrl = `/${slug}/feedback/${feedbackId}`;
|
|
404
|
-
await page.goto(trackingUrl);
|
|
405
|
-
|
|
406
|
-
// Should NOT see internal notes
|
|
407
|
-
await expect(page.locator('[data-testid="internal-notes"]')).not.toBeVisible();
|
|
408
|
-
await expect(page.getByText("Confidential internal note.")).not.toBeVisible();
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
test.skip("edits existing internal note", async () => {});
|
|
412
|
-
|
|
413
|
-
test("deletes internal note", async ({ page }) => {
|
|
414
|
-
// Add a note first
|
|
415
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
416
|
-
|
|
417
|
-
const noteTextarea = page.getByPlaceholder(/添加内部备注|internal note/i);
|
|
418
|
-
await noteTextarea.fill("Note to be deleted.");
|
|
419
|
-
await page.getByRole("button", { name: /添加备注|add/i }).click();
|
|
420
|
-
|
|
421
|
-
// Delete the note
|
|
422
|
-
const deleteNoteButton = page.getByRole("button", { name: /删除备注|delete/i }).first();
|
|
423
|
-
await deleteNoteButton.click({ force: true });
|
|
424
|
-
|
|
425
|
-
// Verify note is deleted
|
|
426
|
-
await expect(page.getByText("Note to be deleted.")).not.toBeVisible();
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test("validates note content is not empty", async ({ page }) => {
|
|
430
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
431
|
-
|
|
432
|
-
const submitButton = page.getByRole("button", { name: /添加备注|add/i });
|
|
433
|
-
await expect(submitButton).toBeDisabled();
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test.skip("shows note history and edits", async () => {});
|
|
437
|
-
|
|
438
|
-
test("requires permission to view and edit notes", async ({ page, request }) => {
|
|
439
|
-
// Create member user
|
|
440
|
-
const memberEmail = uniqueEmail();
|
|
441
|
-
const memberName = uniqueName();
|
|
442
|
-
|
|
443
|
-
await request.post("/api/auth/register", {
|
|
444
|
-
data: {
|
|
445
|
-
name: memberName,
|
|
446
|
-
email: memberEmail,
|
|
447
|
-
password: "TestPass123!",
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
// Logout admin
|
|
452
|
-
await page.context().clearCookies();
|
|
453
|
-
|
|
454
|
-
// Login as member
|
|
455
|
-
await helpers.login(memberEmail, "TestPass123!");
|
|
456
|
-
|
|
457
|
-
// Try to access feedback
|
|
458
|
-
await page.goto(`/admin/feedback/${feedbackId}`);
|
|
459
|
-
|
|
460
|
-
// Should not see internal notes section
|
|
461
|
-
const notesSection = page.locator('[data-testid="internal-notes"], .internal-notes');
|
|
462
|
-
await expect(notesSection).not.toBeVisible();
|
|
463
|
-
|
|
464
|
-
// Try API call to add note
|
|
465
|
-
const response = await page.request.post(`/api/feedback/${feedbackId}/comments`, {
|
|
466
|
-
data: { content: "Test note", isInternal: true }
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
expect(response.status()).toBe(403);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Echo Team
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { test, expect } from "@playwright/test";
|
|
19
|
-
import { uniqueEmail, uniqueName, uniqueTitle, TestHelpers } from "./helpers/test-utils";
|
|
20
|
-
|
|
21
|
-
test.describe("E2E-UF-002: Submit feedback with file attachment", () => {
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
-
let slug: string;
|
|
24
|
-
const password = "StrongPass123!";
|
|
25
|
-
let helpers: TestHelpers;
|
|
26
|
-
|
|
27
|
-
test.beforeEach(async ({ page, request }) => {
|
|
28
|
-
helpers = new TestHelpers(page, request);
|
|
29
|
-
const email = uniqueEmail();
|
|
30
|
-
const name = uniqueName();
|
|
31
|
-
|
|
32
|
-
slug = await helpers.registerAndLogin(name, email, password);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("submits feedback with file attachment successfully", async ({ page, request }) => {
|
|
36
|
-
const title = uniqueTitle();
|
|
37
|
-
const description = "This feedback includes a file attachment.";
|
|
38
|
-
|
|
39
|
-
const createResponse = await request.post("/api/feedback", {
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
"x-organization-id": helpers.getOrganizationId(),
|
|
43
|
-
},
|
|
44
|
-
data: { title, description },
|
|
45
|
-
});
|
|
46
|
-
expect(createResponse.ok()).toBeTruthy();
|
|
47
|
-
const created = await createResponse.json();
|
|
48
|
-
const trackingUrl = created.data.trackingUrl as string;
|
|
49
|
-
const feedbackId = created.data.feedbackId as number;
|
|
50
|
-
|
|
51
|
-
const fileBuffer = Buffer.alloc(2048, 1);
|
|
52
|
-
const uploadResponse = await request.post("/api/upload", {
|
|
53
|
-
multipart: {
|
|
54
|
-
feedbackId: String(feedbackId),
|
|
55
|
-
files: {
|
|
56
|
-
name: "test-file.pdf",
|
|
57
|
-
mimeType: "application/pdf",
|
|
58
|
-
buffer: fileBuffer,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
expect(uploadResponse.ok()).toBeTruthy();
|
|
63
|
-
|
|
64
|
-
await page.goto(trackingUrl);
|
|
65
|
-
|
|
66
|
-
// Assert: feedback detail page shows the title
|
|
67
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
68
|
-
|
|
69
|
-
// Assert: attachment is visible on the detail page
|
|
70
|
-
const attachmentLink = page.getByRole("link", { name: /test-file\.pdf/ });
|
|
71
|
-
await expect(attachmentLink).toBeVisible();
|
|
72
|
-
|
|
73
|
-
// Assert: can download the attachment
|
|
74
|
-
const downloadPromise = page.waitForEvent('download');
|
|
75
|
-
await attachmentLink.click();
|
|
76
|
-
const download = await downloadPromise;
|
|
77
|
-
expect(download.suggestedFilename()).toBe('test-file.pdf');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("shows attachment size and type information", async ({ page, request }) => {
|
|
81
|
-
const title = uniqueTitle();
|
|
82
|
-
|
|
83
|
-
const createResponse = await request.post("/api/feedback", {
|
|
84
|
-
headers: {
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
"x-organization-id": helpers.getOrganizationId(),
|
|
87
|
-
},
|
|
88
|
-
data: { title, description: "Testing attachment info display." },
|
|
89
|
-
});
|
|
90
|
-
expect(createResponse.ok()).toBeTruthy();
|
|
91
|
-
const created = await createResponse.json();
|
|
92
|
-
const trackingUrl = created.data.trackingUrl as string;
|
|
93
|
-
const feedbackId = created.data.feedbackId as number;
|
|
94
|
-
|
|
95
|
-
const fileBuffer = Buffer.alloc(2048, 1);
|
|
96
|
-
const uploadResponse = await request.post("/api/upload", {
|
|
97
|
-
multipart: {
|
|
98
|
-
feedbackId: String(feedbackId),
|
|
99
|
-
files: {
|
|
100
|
-
name: "test-file.pdf",
|
|
101
|
-
mimeType: "application/pdf",
|
|
102
|
-
buffer: fileBuffer,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
expect(uploadResponse.ok()).toBeTruthy();
|
|
107
|
-
|
|
108
|
-
await page.goto(trackingUrl);
|
|
109
|
-
|
|
110
|
-
// Verify attachment info on detail page
|
|
111
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
112
|
-
const attachmentLink = page.getByRole("link", { name: /test-file\.pdf/ });
|
|
113
|
-
await expect(attachmentLink).toBeVisible();
|
|
114
|
-
await expect(attachmentLink).toContainText(/2(\.0)? KB/);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("removes attachment before submission", async ({ page, request }) => {
|
|
118
|
-
const title = uniqueTitle();
|
|
119
|
-
|
|
120
|
-
const createResponse = await request.post("/api/feedback", {
|
|
121
|
-
headers: {
|
|
122
|
-
"Content-Type": "application/json",
|
|
123
|
-
"x-organization-id": helpers.getOrganizationId(),
|
|
124
|
-
},
|
|
125
|
-
data: { title, description: "Testing attachment removal." },
|
|
126
|
-
});
|
|
127
|
-
expect(createResponse.ok()).toBeTruthy();
|
|
128
|
-
const created = await createResponse.json();
|
|
129
|
-
const trackingUrl = created.data.trackingUrl as string;
|
|
130
|
-
|
|
131
|
-
await page.goto(trackingUrl);
|
|
132
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
133
|
-
await expect(page.getByRole("heading", { name: /附件/ })).toHaveCount(0);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("validates file size limits", async ({ request }) => {
|
|
137
|
-
const title = uniqueTitle();
|
|
138
|
-
|
|
139
|
-
const createResponse = await request.post("/api/feedback", {
|
|
140
|
-
headers: {
|
|
141
|
-
"Content-Type": "application/json",
|
|
142
|
-
"x-organization-id": helpers.getOrganizationId(),
|
|
143
|
-
},
|
|
144
|
-
data: { title, description: "Testing file size validation." },
|
|
145
|
-
});
|
|
146
|
-
expect(createResponse.ok()).toBeTruthy();
|
|
147
|
-
const created = await createResponse.json();
|
|
148
|
-
const feedbackId = created.data.feedbackId as number;
|
|
149
|
-
|
|
150
|
-
const largeBuffer = Buffer.alloc(5 * 1024 * 1024 + 1, 1);
|
|
151
|
-
const uploadResponse = await request.post("/api/upload", {
|
|
152
|
-
multipart: {
|
|
153
|
-
feedbackId: String(feedbackId),
|
|
154
|
-
files: {
|
|
155
|
-
name: "large-file.pdf",
|
|
156
|
-
mimeType: "application/pdf",
|
|
157
|
-
buffer: largeBuffer,
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
expect(uploadResponse.status()).toBe(400);
|
|
163
|
-
const errorBody = await uploadResponse.json();
|
|
164
|
-
expect(errorBody.code).toBe("VALIDATION_ERROR");
|
|
165
|
-
expect(errorBody.details?.[0]?.code).toBe("FILE_TOO_LARGE");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
});
|