@antelopejs/dms 0.7.21 → 0.8.1
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 +55 -0
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -3
- package/dist/implementations/dms/resolver-timeout.js.map +1 -1
- package/dist/implementations/dms-auth/account-emails.d.ts +4 -0
- package/dist/implementations/dms-auth/account-emails.js +62 -0
- package/dist/implementations/dms-auth/account-emails.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +1 -3
- package/dist/implementations/dms-auth/index.js +15 -61
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/pages/settings/users/email-change.js +3 -4
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invites.js +1 -1
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.js +1 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -0
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/test/antelope.test.js +2 -1
- package/dist/test/antelope.test.js.map +1 -1
- package/dist/test/helpers/db.js +19 -11
- package/dist/test/helpers/db.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +4 -2
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js +8 -5
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.js +20 -7
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js +34 -0
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js +49 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js +17 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +2 -2
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js +5 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +12 -2
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +29 -4
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +14 -11
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js +8 -5
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js.map +1 -1
- package/dist/test/unit/utils/email-subjects.test.js +27 -0
- package/dist/test/unit/utils/email-subjects.test.js.map +1 -0
- package/dist/utils/admin-invite-email.js +2 -4
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-change-mail.d.ts +1 -1
- package/dist/utils/email-change-mail.js +11 -6
- package/dist/utils/email-change-mail.js.map +1 -1
- package/dist/utils/email-subjects.d.ts +3 -0
- package/dist/utils/email-subjects.js +32 -0
- package/dist/utils/email-subjects.js.map +1 -0
- package/dist/utils/export-jobs.js +15 -10
- package/dist/utils/export-jobs.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +29 -11
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +17 -0
- package/frontend-vue/layers/dms-layout/app/app.config.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +18 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +16 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +14 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/HStack.vue +16 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/VStack.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/composables/useFooterLinks.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeNotice.vue +13 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeVerification.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailExportReady.vue +23 -12
- package/frontend-vue/layers/dms-layout/app/emails/EmailResetPassword.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailTwoFactor.vue +23 -4
- package/frontend-vue/layers/dms-layout/app/emails/EmailUserValidation.vue +18 -6
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailOTP.vue +8 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +40 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +40 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +11 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +27 -36
- package/frontend-vue/layers/dms-ui/app/build/composables/chart/apexTheme.ts +59 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +0 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryOrder.ts +23 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +19 -15
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/page/pageUrlValues.ts +41 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +2 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +7 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/link.ts +7 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +32 -9
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +9 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +5 -11
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +32 -1
- package/frontend-vue/layers/dms-ui/app/components/check-list/CheckList.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +7 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +14 -6
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +52 -3
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +53 -31
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexPlotOptions.ts +15 -4
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexYRange.ts +123 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +49 -49
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartTheme.ts +20 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/form/types/props.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +8 -6
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/utils/bannerToneIcons.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +26 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -0
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +7 -0
- package/frontend-vue/tests/banner-tone-icon.test.ts +55 -0
- package/frontend-vue/tests/builtin-emails.test.ts +141 -0
- package/frontend-vue/tests/cascader.test.ts +27 -0
- package/frontend-vue/tests/chart-auto-y-range.test.ts +25 -9
- package/frontend-vue/tests/chart-colors.test.ts +8 -0
- package/frontend-vue/tests/chart-resize-after-animation.test.ts +100 -0
- package/frontend-vue/tests/chart-type-defaults.test.ts +128 -0
- package/frontend-vue/tests/check-list-marker.test.ts +58 -0
- package/frontend-vue/tests/confirm-from.test.ts +1 -1
- package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -2
- package/frontend-vue/tests/context-links.test.ts +2 -2
- package/frontend-vue/tests/custom-action-confirm-from.test.ts +2 -2
- package/frontend-vue/tests/download-action-target.test.ts +2 -2
- package/frontend-vue/tests/file-drop-prompt.test.ts +72 -0
- package/frontend-vue/tests/footer-config.test.ts +135 -0
- package/frontend-vue/tests/form-actions.test.ts +7 -0
- package/frontend-vue/tests/form-enclosing-form.test.ts +0 -21
- package/frontend-vue/tests/form-entry-order.test.ts +45 -0
- package/frontend-vue/tests/form-footer-kind.test.ts +11 -6
- package/frontend-vue/tests/form-load-error.test.ts +38 -23
- package/frontend-vue/tests/grid-responsive-columns.test.ts +39 -31
- package/frontend-vue/tests/header-actions.test.ts +55 -9
- package/frontend-vue/tests/instant-save-badge.test.ts +11 -4
- package/frontend-vue/tests/list-row-truncate.test.ts +46 -0
- package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +1 -28
- package/frontend-vue/tests/period-live-pause.test.ts +0 -13
- package/frontend-vue/tests/period-max-days.test.ts +0 -14
- package/frontend-vue/tests/roles-name-errors.test.ts +2 -3
- package/frontend-vue/tests/row-side-panel.test.ts +0 -12
- package/frontend-vue/tests/save-bar.test.ts +15 -3
- package/frontend-vue/tests/save-status-error.test.ts +2 -2
- package/frontend-vue/tests/table-cell-wrap.test.ts +0 -12
- package/frontend-vue/tests/table-density-menu.test.ts +0 -21
- package/frontend-vue/tests/table-live-hold.test.ts +0 -44
- package/frontend-vue/tests/table-row-action-errors.test.ts +5 -5
- package/frontend-vue/tests/table-row-hover.test.ts +2 -12
- package/frontend-vue/tests/table-scroll-area.test.ts +0 -8
- package/frontend-vue/tests/table-view-card-props.test.ts +0 -17
- package/frontend-vue/tests/table-view-cards-reorder.test.ts +0 -16
- package/frontend-vue/tests/table-view-grouped.test.ts +0 -55
- package/frontend-vue/tests/table-view-pinned-rows.test.ts +0 -15
- package/frontend-vue/tests/table-view-record-link.test.ts +0 -17
- package/frontend-vue/tests/table-view-refresh.test.ts +0 -17
- package/frontend-vue/tests/table-view-sources.test.ts +1 -1
- package/frontend-vue/tests/table-view-url-tab.test.ts +0 -14
- package/frontend-vue/tests/template-tokens.test.ts +235 -0
- package/frontend-vue/tests/tone.test.ts +0 -19
- package/frontend-vue/tests/url-interpolation.test.ts +6 -6
- package/package.json +6 -6
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-pages/REFERENCE.md +2 -2
- package/skills/dms-project/SKILL.md +0 -1
- package/dist/test/integration/auth/login.test.js +0 -57
- package/dist/test/integration/auth/login.test.js.map +0 -1
- package/dist/test/integration/auth/refresh.test.js +0 -59
- package/dist/test/integration/auth/refresh.test.js.map +0 -1
- package/dist/test/integration/auth/sessions.test.js +0 -58
- package/dist/test/integration/auth/sessions.test.js.map +0 -1
- package/dist/test/integration/auth/signup.test.js +0 -99
- package/dist/test/integration/auth/signup.test.js.map +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.d.ts +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.js +0 -137
- package/dist/test/unit/skills/dms-dev-script.test.js.map +0 -1
- package/frontend-vue/tests/form-reload-on-reopen.test.ts +0 -20
- package/frontend-vue/tests/icon-aria-hidden.test.ts +0 -29
- package/frontend-vue/tests/skeleton-paragraph.test.ts +0 -30
- package/frontend-vue/tests/tab-slot-spacing.test.ts +0 -29
- package/frontend-vue/tests/table-empty-body-viewport.test.ts +0 -25
- package/frontend-vue/tests/table-header-placement.test.ts +0 -21
- package/frontend-vue/tests/vue-inventory.test.ts +0 -107
- /package/dist/test/{integration/auth/login.test.d.ts → unit/implementations/dms-html-render/built-in-emails.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/sessions.test.d.ts → unit/interfaces/dms/hook-handler-results.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/refresh.test.d.ts → unit/interfaces/dms-base/query-response-delta.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/signup.test.d.ts → unit/utils/email-subjects.test.d.ts} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Every option naming a value with a token writes it `{{<scope>.<key>}}`, and
|
|
4
|
+
* every URL or link option fills the page's `{{params.X}}` / `{{query.X}}`
|
|
5
|
+
* the same way: an `api` or `exportJob` target as a `page` one, a
|
|
6
|
+
* confirmation's `from`, a block's links, a form's `backTo`.
|
|
7
|
+
*/
|
|
8
|
+
import { createApp, defineComponent, h, reactive, type App } from "vue";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import { useActionTargets } from "../layers/dms-ui/app/build/composables/actions/useActionTargets";
|
|
11
|
+
import {
|
|
12
|
+
interpolateRowText,
|
|
13
|
+
interpolateUrl,
|
|
14
|
+
} from "../layers/dms-core/app/utils/url-interpolation";
|
|
15
|
+
import { HttpMethod } from "../layers/dms-core/app/types/http";
|
|
16
|
+
import {
|
|
17
|
+
replaceUrlVariables,
|
|
18
|
+
resolveLinkVariables,
|
|
19
|
+
} from "../layers/dms-ui/app/build/utils/urlVariables";
|
|
20
|
+
import {
|
|
21
|
+
pageLink,
|
|
22
|
+
pageRequestUrl,
|
|
23
|
+
} from "../layers/dms-ui/app/build/composables/page/pageUrlValues";
|
|
24
|
+
import { buttonLinkProps } from "../layers/dms-ui/app/build/utils/link";
|
|
25
|
+
import { resolveConfirmDialog } from "../layers/dms-ui/app/build/composables/confirm/confirmDialogTexts";
|
|
26
|
+
import type { CustomButton } from "../layers/dms-ui/app/composables/table-view/types/custom-button";
|
|
27
|
+
import type { CustomRowAction } from "../layers/dms-ui/app/types/row-action";
|
|
28
|
+
|
|
29
|
+
const route = reactive<{ path: string; query: Record<string, unknown> }>({
|
|
30
|
+
path: "/modules/shop/stores/s1",
|
|
31
|
+
query: {},
|
|
32
|
+
});
|
|
33
|
+
const api = vi.fn();
|
|
34
|
+
const runJob = vi.fn();
|
|
35
|
+
let app: App | undefined;
|
|
36
|
+
|
|
37
|
+
function mountTargets() {
|
|
38
|
+
let targets: ReturnType<typeof useActionTargets> | undefined;
|
|
39
|
+
app = createApp(
|
|
40
|
+
defineComponent({
|
|
41
|
+
setup() {
|
|
42
|
+
targets = useActionTargets({
|
|
43
|
+
api: api as never,
|
|
44
|
+
pageId: "store-page",
|
|
45
|
+
componentId: "orders",
|
|
46
|
+
handleApiError: vi.fn(),
|
|
47
|
+
});
|
|
48
|
+
return () => h("div");
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
app.mount(document.createElement("div"));
|
|
53
|
+
return targets!;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
route.query = { store: "north", period: "7d" };
|
|
58
|
+
api.mockReset().mockResolvedValue(undefined);
|
|
59
|
+
runJob.mockReset().mockResolvedValue(undefined);
|
|
60
|
+
vi.stubGlobal("useToast", () => ({ add: vi.fn() }));
|
|
61
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
62
|
+
processI18n: (text: string) => text,
|
|
63
|
+
processApiMessage: (text: string) => text,
|
|
64
|
+
}));
|
|
65
|
+
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
|
|
66
|
+
vi.stubGlobal("useModal", () => ({ open: vi.fn() }));
|
|
67
|
+
vi.stubGlobal("useDrawer", () => ({ open: vi.fn() }));
|
|
68
|
+
vi.stubGlobal("useExportJob", () => ({ runJob }));
|
|
69
|
+
vi.stubGlobal("useConfirm", () => ({ confirm: vi.fn(async () => true) }));
|
|
70
|
+
vi.stubGlobal("useAuthFetch", () => ({
|
|
71
|
+
$authFetch: Object.assign(vi.fn(), { raw: vi.fn() }),
|
|
72
|
+
}));
|
|
73
|
+
vi.stubGlobal("interpolateUrl", interpolateUrl);
|
|
74
|
+
vi.stubGlobal("HttpMethod", HttpMethod);
|
|
75
|
+
vi.stubGlobal("useDmsRoute", () => route);
|
|
76
|
+
vi.stubGlobal("useSiteLayout", () => ({
|
|
77
|
+
findMatchingRoute: () => ({ params: { id: "s1" } }),
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
app?.unmount();
|
|
83
|
+
app = undefined;
|
|
84
|
+
vi.restoreAllMocks();
|
|
85
|
+
vi.unstubAllGlobals();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("token syntax", () => {
|
|
89
|
+
const context = {
|
|
90
|
+
routeParams: { id: "s 1" },
|
|
91
|
+
routeQuery: { store: "north" },
|
|
92
|
+
response: { data: { id: "o-9" } },
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
it("allows spaces inside the braces, as the server's filter tokens do", () => {
|
|
96
|
+
expect(
|
|
97
|
+
replaceUrlVariables(
|
|
98
|
+
"/s/{{ params.id }}?store={{ query.store }}",
|
|
99
|
+
context,
|
|
100
|
+
),
|
|
101
|
+
).toBe("/s/s%201?store=north");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("reaches into a response with a dotted path", () => {
|
|
105
|
+
expect(replaceUrlVariables("/orders/{{response.data.id}}", context)).toBe(
|
|
106
|
+
"/orders/o-9",
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("resolves a redirect's response and page tokens, dropping an empty pair", () => {
|
|
111
|
+
expect(
|
|
112
|
+
resolveLinkVariables(
|
|
113
|
+
"/orders/{{response.data.id}}?store={{query.store}}&tab={{query.tab}}",
|
|
114
|
+
context,
|
|
115
|
+
),
|
|
116
|
+
).toBe("/orders/o-9?store=north");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("names a row field with {{row.X}}, a dotted path reaching into it", () => {
|
|
120
|
+
const row = { _id: "a 1", customer: { name: "Ada" } };
|
|
121
|
+
expect(interpolateUrl("/o/{{row._id}}/{{ row.customer.name }}", row)).toBe(
|
|
122
|
+
"/o/a%201/Ada",
|
|
123
|
+
);
|
|
124
|
+
expect(interpolateRowText("{{row._id}}.csv", row)).toBe("a 1.csv");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("does not take a single-brace name for a row field", () => {
|
|
128
|
+
expect(interpolateUrl("/o/{_id}", { _id: "a" })).toBe("/o/{_id}");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("leaves another scope's token whole when filling a row", () => {
|
|
132
|
+
expect(
|
|
133
|
+
interpolateUrl("/s/{{params.id}}/o/{{row._id}}", { _id: "a" }),
|
|
134
|
+
).toBe("/s/{{params.id}}/o/a");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("page tokens in links and requested routes", () => {
|
|
139
|
+
it("fills a block link, leaving out a pair the page has no value for", () => {
|
|
140
|
+
expect(pageLink("/stores/{{params.id}}?store={{query.store}}")).toBe(
|
|
141
|
+
"/stores/s1?store=north",
|
|
142
|
+
);
|
|
143
|
+
expect(pageLink("/orders?tab={{query.tab}}")).toBe("/orders");
|
|
144
|
+
expect(buttonLinkProps("/stores/{{params.id}}/orders")).toEqual({
|
|
145
|
+
to: "/stores/s1/orders",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("does not request a route while one of its tokens has no value", () => {
|
|
150
|
+
expect(pageRequestUrl("/api/stores/{{params.id}}/rotate")).toBe(
|
|
151
|
+
"/api/stores/s1/rotate",
|
|
152
|
+
);
|
|
153
|
+
expect(pageRequestUrl("/api/x?tab={{query.tab}}")).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("fills a confirmation's secondary target with the page, then the row", async () => {
|
|
157
|
+
const runTarget = vi.fn();
|
|
158
|
+
const dialog = resolveConfirmDialog(
|
|
159
|
+
{
|
|
160
|
+
title: "Remove",
|
|
161
|
+
secondaryAction: {
|
|
162
|
+
label: "Download",
|
|
163
|
+
target: {
|
|
164
|
+
type: "download",
|
|
165
|
+
url: "/api/stores/{{params.id}}/orders/{{row._id}}",
|
|
166
|
+
filename: "{{query.store}}-{{row.name}}.json",
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{ _id: "o1", name: "order" },
|
|
171
|
+
(key) => key,
|
|
172
|
+
runTarget,
|
|
173
|
+
);
|
|
174
|
+
await dialog.secondaryAction!.run();
|
|
175
|
+
expect(runTarget).toHaveBeenCalledWith({
|
|
176
|
+
type: "download",
|
|
177
|
+
url: "/api/stores/s1/orders/o1",
|
|
178
|
+
filename: "north-order.json",
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("an action target's URL", () => {
|
|
184
|
+
it("fills an api target on a row with the page's values, then the row's", async () => {
|
|
185
|
+
const action: CustomRowAction = {
|
|
186
|
+
label: "Refund",
|
|
187
|
+
target: {
|
|
188
|
+
type: "api",
|
|
189
|
+
url: "/api/stores/{{params.id}}/orders/{{row._id}}/refund?store={{query.store}}",
|
|
190
|
+
successMessage: "Refunded",
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
mountTargets().handleCustomRowAction(action, { _id: "o 1" });
|
|
194
|
+
await vi.waitFor(() => expect(api).toHaveBeenCalled());
|
|
195
|
+
expect(api.mock.calls[0]![0]).toBe(
|
|
196
|
+
"/api/stores/s1/orders/o%201/refund?store=north",
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("fills an api target on a toolbar button, without a row", async () => {
|
|
201
|
+
const button: CustomButton = {
|
|
202
|
+
id: "sync",
|
|
203
|
+
label: "Sync",
|
|
204
|
+
target: {
|
|
205
|
+
type: "api",
|
|
206
|
+
url: "/api/stores/{{params.id}}/sync?period={{query.period}}",
|
|
207
|
+
successMessage: "Synced",
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
mountTargets().handleCustomButton(button);
|
|
211
|
+
await vi.waitFor(() => expect(api).toHaveBeenCalled());
|
|
212
|
+
expect(api.mock.calls[0]![0]).toBe("/api/stores/s1/sync?period=7d");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("fills an export job's start, status and download URLs", async () => {
|
|
216
|
+
const button: CustomButton = {
|
|
217
|
+
id: "export",
|
|
218
|
+
label: "Export",
|
|
219
|
+
target: {
|
|
220
|
+
type: "exportJob",
|
|
221
|
+
url: "/api/stores/{{params.id}}/export?store={{query.store}}",
|
|
222
|
+
statusUrl: "/api/stores/{{params.id}}/export/{{job.id}}",
|
|
223
|
+
downloadUrl: "/api/stores/{{params.id}}/export/{{ job.id }}/file",
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
mountTargets().handleCustomButton(button);
|
|
227
|
+
await vi.waitFor(() => expect(runJob).toHaveBeenCalled());
|
|
228
|
+
const job = runJob.mock.calls[0]![0];
|
|
229
|
+
expect(job.startUrl).toBe("/api/stores/s1/export?store=north");
|
|
230
|
+
expect(job.statusUrl({ jobId: "j1" })).toBe("/api/stores/s1/export/j1");
|
|
231
|
+
expect(job.downloadUrl({ jobId: "j1" })).toBe(
|
|
232
|
+
"/api/stores/s1/export/j1/file",
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { join, relative } from "node:path";
|
|
4
2
|
import { describe, expect, it } from "vitest";
|
|
5
3
|
import { createSSRApp } from "vue";
|
|
6
4
|
import { renderToString } from "vue/server-renderer";
|
|
@@ -10,15 +8,6 @@ import {
|
|
|
10
8
|
isDmsTone,
|
|
11
9
|
} from "../layers/dms-ui/app/build/utils/tone";
|
|
12
10
|
|
|
13
|
-
const FORMER_TONE = /tone"?\s*[=:][^\n]*["']accent["']/i;
|
|
14
|
-
|
|
15
|
-
function walk(directory: string): string[] {
|
|
16
|
-
return readdirSync(directory).flatMap((name) => {
|
|
17
|
-
const path = join(directory, name);
|
|
18
|
-
return statSync(path).isDirectory() ? walk(path) : [path];
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
11
|
describe("Tone", () => {
|
|
23
12
|
it("knows the semantic tones only", () => {
|
|
24
13
|
expect(isDmsTone("success")).toBe(true);
|
|
@@ -34,12 +23,4 @@ describe("Tone", () => {
|
|
|
34
23
|
expect(html).toContain(name);
|
|
35
24
|
}
|
|
36
25
|
});
|
|
37
|
-
|
|
38
|
-
it("is never passed as accent by a DMS component", () => {
|
|
39
|
-
const offenders = walk(join(process.cwd(), "layers"))
|
|
40
|
-
.filter((path) => path.endsWith(".vue"))
|
|
41
|
-
.filter((path) => FORMER_TONE.test(readFileSync(path, "utf8")))
|
|
42
|
-
.map((path) => relative(process.cwd(), path));
|
|
43
|
-
expect(offenders).toEqual([]);
|
|
44
|
-
});
|
|
45
26
|
});
|
|
@@ -4,25 +4,25 @@ import { interpolateUrl } from "../layers/dms-core/app/utils/url-interpolation";
|
|
|
4
4
|
describe("interpolateUrl", () => {
|
|
5
5
|
it("fills row fields into the URL, encoded", () => {
|
|
6
6
|
expect(
|
|
7
|
-
interpolateUrl("/items/{_id}/tag/{name}", { _id: "a1", name: "a b/c" }),
|
|
7
|
+
interpolateUrl("/items/{{row._id}}/tag/{{row.name}}", { _id: "a1", name: "a b/c" }),
|
|
8
8
|
).toBe("/items/a1/tag/a%20b%2Fc");
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it("keeps the colon of a tenant-scoped id readable", () => {
|
|
12
12
|
expect(
|
|
13
|
-
interpolateUrl("/members/{_id}/owner", { _id: "default:29fd-11" }),
|
|
13
|
+
interpolateUrl("/members/{{row._id}}/owner", { _id: "default:29fd-11" }),
|
|
14
14
|
).toBe("/members/default:29fd-11/owner");
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it("leaves a missing field empty", () => {
|
|
18
|
-
expect(interpolateUrl("/items/{missing}", {})).toBe("/items/");
|
|
18
|
+
expect(interpolateUrl("/items/{{row.missing}}", {})).toBe("/items/");
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it("keeps web and mail links", () => {
|
|
22
|
-
expect(interpolateUrl("https://acme.dev/{slug}", { slug: "a" })).toBe(
|
|
22
|
+
expect(interpolateUrl("https://acme.dev/{{row.slug}}", { slug: "a" })).toBe(
|
|
23
23
|
"https://acme.dev/a",
|
|
24
24
|
);
|
|
25
|
-
expect(interpolateUrl("mailto:{email}", { email: "a@b.c" })).toBe(
|
|
25
|
+
expect(interpolateUrl("mailto:{{row.email}}", { email: "a@b.c" })).toBe(
|
|
26
26
|
"mailto:a%40b.c",
|
|
27
27
|
);
|
|
28
28
|
});
|
|
@@ -34,7 +34,7 @@ describe("interpolateUrl", () => {
|
|
|
34
34
|
"data:text/html,x",
|
|
35
35
|
"vbscript:x",
|
|
36
36
|
]) {
|
|
37
|
-
expect(interpolateUrl("{site}", { site })).toBe("");
|
|
37
|
+
expect(interpolateUrl("{{row.site}}", { site })).toBe("");
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/AntelopeJS/dms.git"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
|
|
38
38
|
"@antelopejs/interface-database": ">=0.1.8 <1.0.0",
|
|
39
39
|
"@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
|
|
40
|
-
"@antelopejs/interface-dms": ">=0.
|
|
40
|
+
"@antelopejs/interface-dms": ">=0.6.0 <0.7.0",
|
|
41
41
|
"@antelopejs/interface-email": ">=0.0.5 <1.0.0",
|
|
42
42
|
"@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
|
|
43
43
|
"archiver": "^7.0.1",
|
|
@@ -110,10 +110,10 @@
|
|
|
110
110
|
"frontend:dev": "pnpm --dir playground frontend:dev",
|
|
111
111
|
"lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt . && pnpm --dir frontend-vue lint:fix",
|
|
112
112
|
"lint": "oxlint && oxfmt --check . && pnpm --dir frontend-vue lint && antelopejs-check-interface-ranges",
|
|
113
|
-
"typecheck": "pnpm --filter @antelopejs/interface-dms build && tsc --noEmit",
|
|
114
|
-
"test": "pnpm run build && ajs module test .",
|
|
115
|
-
"test:unit": "pnpm run build && DMS_TEST_DIR=dist/test/unit ajs module test .",
|
|
116
|
-
"test:integration": "pnpm run build && DMS_TEST_DIR=dist/test/integration ajs module test .",
|
|
113
|
+
"typecheck": "node scripts/unless-prebuilt.mjs pnpm --filter @antelopejs/interface-dms build && tsc --noEmit",
|
|
114
|
+
"test": "node scripts/unless-prebuilt.mjs pnpm run build && ajs module test .",
|
|
115
|
+
"test:unit": "node scripts/unless-prebuilt.mjs pnpm run build && DMS_TEST_DIR=dist/test/unit ajs module test .",
|
|
116
|
+
"test:integration": "node scripts/unless-prebuilt.mjs pnpm run build && DMS_TEST_DIR=dist/test/integration ajs module test .",
|
|
117
117
|
"test:watch": "tsc -w & tsc-alias -w",
|
|
118
118
|
"release": "pnpm --filter @antelopejs/interface-dms build && pnpm --dir frontend-vue install --frozen-lockfile && release-it",
|
|
119
119
|
"country-database": "node scripts/fetch-country-database.mjs",
|
package/skills/dms/SKILL.md
CHANGED
|
@@ -33,7 +33,8 @@ For the framework underneath (the core, `ajs` CLI, interfaces as versioned contr
|
|
|
33
33
|
frontend renders it. **Components fetch their own data** via URLs (`fetchUrl`, `submitUrl`, …) —
|
|
34
34
|
routes often declared `@Get`/`@Post` on the same page class; the backend stays the source of truth.
|
|
35
35
|
A URL names the record of its page with `{{params.id}}` / `{{query.X}}` (filled from the page URL, percent-encoded;
|
|
36
|
-
nothing is fetched while one is unresolved)
|
|
36
|
+
nothing is fetched while one is unresolved) — every token is `{{<scope>.<key>}}`: `params`, `query`, `row` (an
|
|
37
|
+
action's row), `response` (a form's redirect), and the same page tokens work in every URL and link option, and `.watch(FormEvents.SUBMIT_SUCCESS,
|
|
37
38
|
BlockFunctions.REFRESH_PAGE)` makes the page's data blocks (table views included) re-fetch after a form saves;
|
|
38
39
|
`periodScope` binds a card, chart, list block, `Meter` or `TableView.fromSource` to a `PeriodSelector`
|
|
39
40
|
(which keeps its period in the page URL, `?<id>.period=`), and
|
|
@@ -54,10 +54,10 @@ own drawer in a `CustomComponent`.
|
|
|
54
54
|
To read rows one after the other next to the table (a request log), use a `sidePanel`
|
|
55
55
|
target instead: a non-modal panel docked on the right whose header the DMS draws (title,
|
|
56
56
|
`rowTitle` naming the row from its fields, `positionLabel` "x of y", previous / next, J / K, close), `?record=` kept by default (`deepLink: false` opts out), a "not found" state on a 404 (`notFound: { title, description }`) and the table's
|
|
57
|
-
`rowClick: "single"` (a `TableView.fromSource` table reads a deep-linked row beyond its first page through `fetchRowUrl: "/api/x/{id}"`) — never bridge the table's DOM to build one —
|
|
57
|
+
`rowClick: "single"` (a `TableView.fromSource` table reads a deep-linked row beyond its first page through `fetchRowUrl: "/api/x/{{row.id}}"`) — never bridge the table's DOM to build one —
|
|
58
58
|
`docs/04.components/06.tables/8.row-drawers.md#side-panel`.
|
|
59
59
|
A button downloading a file the user's session may read (`/api/...`) takes a `download`
|
|
60
|
-
target, `{ url, filename? }`, with the same `{field}` / `{{query.X}}` tokens — never a Vue
|
|
60
|
+
target, `{ url, filename? }`, with the same `{{row.field}}` / `{{query.X}}` tokens — never a Vue
|
|
61
61
|
header button or an `api` target for it — `docs/07.interfaces/3.base/2.types.md`.
|
|
62
62
|
|
|
63
63
|
A row that must stay on top of a table under any sort (a base language, a default
|
|
@@ -82,7 +82,6 @@ export function construct(config: Config): void {
|
|
|
82
82
|
registerAllPermissions(); // RegisterPermission(...) — see dms-auth
|
|
83
83
|
RegisterHook(Hook.DATABASE_INITIALIZED, async () => {
|
|
84
84
|
await initDatabase();
|
|
85
|
-
return undefined;
|
|
86
85
|
});
|
|
87
86
|
}
|
|
88
87
|
export async function start(): Promise<void> {
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const auth_1 = require("../../helpers/auth");
|
|
5
|
-
const db_1 = require("../../helpers/db");
|
|
6
|
-
const http_1 = require("../../helpers/http");
|
|
7
|
-
describe.skip("[integration] auth/login", () => {
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
await (0, db_1.resetDatabase)();
|
|
10
|
-
});
|
|
11
|
-
it("logs in a registered user and returns a bearer token", async () => {
|
|
12
|
-
const user = await (0, auth_1.registerUser)();
|
|
13
|
-
const client = (0, http_1.createClient)();
|
|
14
|
-
const response = await client.post("/api/auth/login", {
|
|
15
|
-
email: user.email,
|
|
16
|
-
password: user.password,
|
|
17
|
-
});
|
|
18
|
-
(0, chai_1.expect)(response.status).to.equal(200);
|
|
19
|
-
(0, chai_1.expect)(response.data.token_type).to.equal("Bearer");
|
|
20
|
-
(0, chai_1.expect)(response.data.access_token).to.be.a("string").and.not.empty;
|
|
21
|
-
(0, chai_1.expect)(response.data.refresh_token).to.be.a("string").and.not.empty;
|
|
22
|
-
(0, chai_1.expect)(response.data.user?.email).to.equal(user.email);
|
|
23
|
-
});
|
|
24
|
-
it("rejects a wrong password with 401", async () => {
|
|
25
|
-
const user = await (0, auth_1.registerUser)();
|
|
26
|
-
const client = (0, http_1.createClient)();
|
|
27
|
-
const response = await client.post("/api/auth/login", {
|
|
28
|
-
email: user.email,
|
|
29
|
-
password: "WrongPassw0rd!",
|
|
30
|
-
});
|
|
31
|
-
(0, chai_1.expect)(response.status).to.equal(401);
|
|
32
|
-
});
|
|
33
|
-
it("rejects an unknown email with 401", async () => {
|
|
34
|
-
const client = (0, http_1.createClient)();
|
|
35
|
-
const response = await client.post("/api/auth/login", {
|
|
36
|
-
email: "nobody@test.local",
|
|
37
|
-
password: "WhateverP@ss1",
|
|
38
|
-
});
|
|
39
|
-
(0, chai_1.expect)(response.status).to.equal(401);
|
|
40
|
-
});
|
|
41
|
-
it("rejects a malformed payload with 400", async () => {
|
|
42
|
-
const client = (0, http_1.createClient)();
|
|
43
|
-
const response = await client.post("/api/auth/login", {
|
|
44
|
-
email: "not-an-email",
|
|
45
|
-
password: "x",
|
|
46
|
-
});
|
|
47
|
-
(0, chai_1.expect)(response.status).to.equal(400);
|
|
48
|
-
});
|
|
49
|
-
it("rejects a missing password with 400", async () => {
|
|
50
|
-
const client = (0, http_1.createClient)();
|
|
51
|
-
const response = await client.post("/api/auth/login", {
|
|
52
|
-
email: "user@test.local",
|
|
53
|
-
});
|
|
54
|
-
(0, chai_1.expect)(response.status).to.equal(400);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
//# sourceMappingURL=login.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../../../src/test/integration/auth/login.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
5
|
-
const auth_1 = require("../../helpers/auth");
|
|
6
|
-
const db_1 = require("../../helpers/db");
|
|
7
|
-
const http_1 = require("../../helpers/http");
|
|
8
|
-
const REFRESH_URL = "/api/auth/refresh";
|
|
9
|
-
const LOGOUT_URL = "/api/auth/logout";
|
|
10
|
-
const SUCCESS_STATUS = 200;
|
|
11
|
-
const UNAUTH_STATUS = 401;
|
|
12
|
-
const NO_CONTENT_STATUS = 204;
|
|
13
|
-
const BEARER = "Bearer";
|
|
14
|
-
const FAKE_USER_ID = "000000000000000000000000";
|
|
15
|
-
const FAKE_NAMESPACE = "default";
|
|
16
|
-
const FAKE_SECRET = "fake-secret";
|
|
17
|
-
const EXPIRED_EXPIRES_IN = -1;
|
|
18
|
-
function buildExpiredToken() {
|
|
19
|
-
return (0, jsonwebtoken_1.sign)({ id: FAKE_USER_ID, namespace: FAKE_NAMESPACE }, FAKE_SECRET, {
|
|
20
|
-
expiresIn: EXPIRED_EXPIRES_IN,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
describe.skip("[integration] auth/refresh", () => {
|
|
24
|
-
beforeEach(async () => {
|
|
25
|
-
await (0, db_1.resetDatabase)();
|
|
26
|
-
});
|
|
27
|
-
it("returns a new access token from a valid refresh token", async () => {
|
|
28
|
-
const user = await (0, auth_1.registerUser)();
|
|
29
|
-
const client = (0, http_1.createClient)();
|
|
30
|
-
const response = await client.post(REFRESH_URL, {
|
|
31
|
-
token: user.refreshToken,
|
|
32
|
-
});
|
|
33
|
-
(0, chai_1.expect)(response.status).to.equal(SUCCESS_STATUS);
|
|
34
|
-
(0, chai_1.expect)(response.data.token_type).to.equal(BEARER);
|
|
35
|
-
(0, chai_1.expect)(response.data.access_token).to.be.a("string").and.not.empty;
|
|
36
|
-
(0, chai_1.expect)(response.data.refresh_token).to.be.a("string").and.not.empty;
|
|
37
|
-
});
|
|
38
|
-
it("rejects an expired refresh token referencing an unknown user with 401", async () => {
|
|
39
|
-
const client = (0, http_1.createClient)();
|
|
40
|
-
const response = await client.post(REFRESH_URL, {
|
|
41
|
-
token: buildExpiredToken(),
|
|
42
|
-
});
|
|
43
|
-
(0, chai_1.expect)(response.status).to.equal(UNAUTH_STATUS);
|
|
44
|
-
});
|
|
45
|
-
it("rejects a refresh token whose session has been revoked with 401", async () => {
|
|
46
|
-
const user = await (0, auth_1.registerUser)();
|
|
47
|
-
const authClient = (0, auth_1.authorizedClient)(user.accessToken);
|
|
48
|
-
const logoutResponse = await authClient.post(LOGOUT_URL, {
|
|
49
|
-
token: user.refreshToken,
|
|
50
|
-
});
|
|
51
|
-
(0, chai_1.expect)(logoutResponse.status).to.equal(NO_CONTENT_STATUS);
|
|
52
|
-
const client = (0, http_1.createClient)();
|
|
53
|
-
const response = await client.post(REFRESH_URL, {
|
|
54
|
-
token: user.refreshToken,
|
|
55
|
-
});
|
|
56
|
-
(0, chai_1.expect)(response.status).to.equal(UNAUTH_STATUS);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
//# sourceMappingURL=refresh.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.test.js","sourceRoot":"","sources":["../../../../src/test/integration/auth/refresh.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const auth_1 = require("../../helpers/auth");
|
|
5
|
-
const db_1 = require("../../helpers/db");
|
|
6
|
-
const http_1 = require("../../helpers/http");
|
|
7
|
-
const SESSIONS_URL = "/settings/user/profile/sessions";
|
|
8
|
-
const LOGOUT_URL = "/api/auth/logout";
|
|
9
|
-
const UNAUTH_STATUS = 401;
|
|
10
|
-
const SUCCESS_STATUS = 200;
|
|
11
|
-
const NO_CONTENT_STATUS = 204;
|
|
12
|
-
describe.skip("[integration] auth/sessions", () => {
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
await (0, db_1.resetDatabase)();
|
|
15
|
-
});
|
|
16
|
-
it("lists the current session after login", async () => {
|
|
17
|
-
const user = await (0, auth_1.registerUser)({ owner: true });
|
|
18
|
-
const session = await (0, auth_1.loginUser)(user.email, user.password);
|
|
19
|
-
const client = (0, auth_1.authorizedClient)(session.accessToken);
|
|
20
|
-
const response = await client.get(SESSIONS_URL);
|
|
21
|
-
(0, chai_1.expect)(response.status).to.equal(SUCCESS_STATUS);
|
|
22
|
-
(0, chai_1.expect)(response.data).to.be.an("array");
|
|
23
|
-
(0, chai_1.expect)(response.data.length).to.be.at.least(1);
|
|
24
|
-
const current = response.data.find((entry) => entry.isCurrent);
|
|
25
|
-
(0, chai_1.expect)(current).to.exist;
|
|
26
|
-
});
|
|
27
|
-
it("logout deletes the current session from the sessions list", async () => {
|
|
28
|
-
const user = await (0, auth_1.registerUser)({ owner: true });
|
|
29
|
-
const session = await (0, auth_1.loginUser)(user.email, user.password);
|
|
30
|
-
const client = (0, auth_1.authorizedClient)(session.accessToken);
|
|
31
|
-
const beforeLogout = await client.get(SESSIONS_URL);
|
|
32
|
-
(0, chai_1.expect)(beforeLogout.status).to.equal(SUCCESS_STATUS);
|
|
33
|
-
const initialCount = beforeLogout.data.length;
|
|
34
|
-
const currentBefore = beforeLogout.data.find((entry) => entry.isCurrent);
|
|
35
|
-
(0, chai_1.expect)(currentBefore).to.exist;
|
|
36
|
-
const logoutResponse = await client.post(LOGOUT_URL, {
|
|
37
|
-
token: session.refreshToken,
|
|
38
|
-
});
|
|
39
|
-
(0, chai_1.expect)(logoutResponse.status).to.equal(NO_CONTENT_STATUS);
|
|
40
|
-
const fallbackClient = (0, auth_1.authorizedClient)(user.accessToken);
|
|
41
|
-
const afterLogout = await fallbackClient.get(SESSIONS_URL);
|
|
42
|
-
(0, chai_1.expect)(afterLogout.status).to.equal(SUCCESS_STATUS);
|
|
43
|
-
(0, chai_1.expect)(afterLogout.data.length).to.equal(initialCount - 1);
|
|
44
|
-
const revokedStillPresent = afterLogout.data.some((entry) => entry._id === currentBefore._id);
|
|
45
|
-
(0, chai_1.expect)(revokedStillPresent).to.equal(false);
|
|
46
|
-
});
|
|
47
|
-
it("rejects unauthenticated GET /sessions with 401", async () => {
|
|
48
|
-
const client = (0, http_1.createClient)();
|
|
49
|
-
const response = await client.get(SESSIONS_URL);
|
|
50
|
-
(0, chai_1.expect)(response.status).to.equal(UNAUTH_STATUS);
|
|
51
|
-
});
|
|
52
|
-
it("rejects unauthenticated DELETE /sessions with 401", async () => {
|
|
53
|
-
const client = (0, http_1.createClient)();
|
|
54
|
-
const response = await client.delete(SESSIONS_URL);
|
|
55
|
-
(0, chai_1.expect)(response.status).to.equal(UNAUTH_STATUS);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
//# sourceMappingURL=sessions.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.test.js","sourceRoot":"","sources":["../../../../src/test/integration/auth/sessions.test.ts"],"names":[],"mappings":"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"}
|