@htlkg/core 0.0.2 → 0.0.3
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/README.md +51 -0
- package/dist/index.d.ts +219 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -8
- package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
- package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
- package/src/amplify-astro-adapter/errors.test.ts +115 -0
- package/src/amplify-astro-adapter/errors.ts +105 -0
- package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
- package/src/amplify-astro-adapter/globalSettings.ts +16 -0
- package/src/amplify-astro-adapter/index.ts +14 -0
- package/src/amplify-astro-adapter/types.ts +55 -0
- package/src/auth/auth.md +178 -0
- package/src/auth/index.test.ts +180 -0
- package/src/auth/index.ts +294 -0
- package/src/constants/constants.md +132 -0
- package/src/constants/index.test.ts +116 -0
- package/src/constants/index.ts +98 -0
- package/src/core-exports.property.test.ts +186 -0
- package/src/errors/errors.md +177 -0
- package/src/errors/index.test.ts +153 -0
- package/src/errors/index.ts +134 -0
- package/src/index.ts +65 -0
- package/src/routes/index.ts +225 -0
- package/src/routes/routes.md +189 -0
- package/src/types/index.ts +94 -0
- package/src/types/types.md +144 -0
- package/src/utils/index.test.ts +257 -0
- package/src/utils/index.ts +112 -0
- package/src/utils/logger.ts +88 -0
- package/src/utils/utils.md +199 -0
- package/src/workspace.property.test.ts +235 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ROUTES, PRODUCTS, PERMISSIONS, USER_ROLES } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("Constants", () => {
|
|
5
|
+
describe("ROUTES", () => {
|
|
6
|
+
it("should have admin routes", () => {
|
|
7
|
+
expect(ROUTES.ADMIN.HOME).toBe("/admin");
|
|
8
|
+
expect(ROUTES.ADMIN.BRANDS).toBe("/admin/brands");
|
|
9
|
+
expect(ROUTES.ADMIN.ACCOUNTS).toBe("/admin/accounts");
|
|
10
|
+
expect(ROUTES.ADMIN.USERS).toBe("/admin/users");
|
|
11
|
+
expect(ROUTES.ADMIN.PRODUCTS).toBe("/admin/products");
|
|
12
|
+
expect(ROUTES.ADMIN.SETTINGS).toBe("/admin/settings");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should have brand route functions", () => {
|
|
16
|
+
expect(ROUTES.BRAND.HOME("test-brand")).toBe("/test-brand");
|
|
17
|
+
expect(ROUTES.BRAND.ADMIN("test-brand")).toBe("/test-brand/admin");
|
|
18
|
+
expect(ROUTES.BRAND.TEMPLATES("test-brand")).toBe(
|
|
19
|
+
"/test-brand/admin/templates",
|
|
20
|
+
);
|
|
21
|
+
expect(ROUTES.BRAND.SETTINGS("test-brand")).toBe(
|
|
22
|
+
"/test-brand/admin/settings",
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should have auth routes", () => {
|
|
27
|
+
expect(ROUTES.AUTH.LOGIN).toBe("/login");
|
|
28
|
+
expect(ROUTES.AUTH.LOGOUT).toBe("/logout");
|
|
29
|
+
expect(ROUTES.AUTH.SIGNUP).toBe("/signup");
|
|
30
|
+
expect(ROUTES.AUTH.FORGOT_PASSWORD).toBe("/forgot-password");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should be defined as const", () => {
|
|
34
|
+
// Note: TypeScript 'as const' provides compile-time immutability
|
|
35
|
+
// but doesn't prevent runtime mutations. This test just verifies the structure exists.
|
|
36
|
+
expect(ROUTES.ADMIN).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("PRODUCTS", () => {
|
|
41
|
+
it("should have WiFi Portal product", () => {
|
|
42
|
+
expect(PRODUCTS.WIFI_PORTAL.id).toBe("wifi-portal");
|
|
43
|
+
expect(PRODUCTS.WIFI_PORTAL.name).toBe("WiFi Portal");
|
|
44
|
+
expect(PRODUCTS.WIFI_PORTAL.description).toContain("Captive portal");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should have WhatsApp CRM product", () => {
|
|
48
|
+
expect(PRODUCTS.WHATSAPP_CRM.id).toBe("whatsapp-crm");
|
|
49
|
+
expect(PRODUCTS.WHATSAPP_CRM.name).toBe("WhatsApp CRM");
|
|
50
|
+
expect(PRODUCTS.WHATSAPP_CRM.description).toContain("WhatsApp");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should have Analytics product", () => {
|
|
54
|
+
expect(PRODUCTS.ANALYTICS.id).toBe("analytics");
|
|
55
|
+
expect(PRODUCTS.ANALYTICS.name).toBe("Analytics");
|
|
56
|
+
expect(PRODUCTS.ANALYTICS.description).toContain("Analytics");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should be defined as const", () => {
|
|
60
|
+
expect(PRODUCTS.WIFI_PORTAL).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("PERMISSIONS", () => {
|
|
65
|
+
it("should have brand permissions", () => {
|
|
66
|
+
expect(PERMISSIONS.BRAND_VIEW).toBe("brand:view");
|
|
67
|
+
expect(PERMISSIONS.BRAND_EDIT).toBe("brand:edit");
|
|
68
|
+
expect(PERMISSIONS.BRAND_DELETE).toBe("brand:delete");
|
|
69
|
+
expect(PERMISSIONS.BRAND_CREATE).toBe("brand:create");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should have account permissions", () => {
|
|
73
|
+
expect(PERMISSIONS.ACCOUNT_VIEW).toBe("account:view");
|
|
74
|
+
expect(PERMISSIONS.ACCOUNT_EDIT).toBe("account:edit");
|
|
75
|
+
expect(PERMISSIONS.ACCOUNT_DELETE).toBe("account:delete");
|
|
76
|
+
expect(PERMISSIONS.ACCOUNT_CREATE).toBe("account:create");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should have user permissions", () => {
|
|
80
|
+
expect(PERMISSIONS.USER_VIEW).toBe("user:view");
|
|
81
|
+
expect(PERMISSIONS.USER_EDIT).toBe("user:edit");
|
|
82
|
+
expect(PERMISSIONS.USER_DELETE).toBe("user:delete");
|
|
83
|
+
expect(PERMISSIONS.USER_CREATE).toBe("user:create");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should have product permissions", () => {
|
|
87
|
+
expect(PERMISSIONS.PRODUCT_VIEW).toBe("product:view");
|
|
88
|
+
expect(PERMISSIONS.PRODUCT_EDIT).toBe("product:edit");
|
|
89
|
+
expect(PERMISSIONS.PRODUCT_ENABLE).toBe("product:enable");
|
|
90
|
+
expect(PERMISSIONS.PRODUCT_DISABLE).toBe("product:disable");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should have admin permissions", () => {
|
|
94
|
+
expect(PERMISSIONS.ADMIN_ACCESS).toBe("admin:access");
|
|
95
|
+
expect(PERMISSIONS.SUPER_ADMIN_ACCESS).toBe("super_admin:access");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should be defined as const", () => {
|
|
99
|
+
expect(PERMISSIONS.BRAND_VIEW).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("USER_ROLES", () => {
|
|
104
|
+
it("should have all user roles", () => {
|
|
105
|
+
expect(USER_ROLES.SUPER_ADMIN).toBe("SUPER_ADMINS");
|
|
106
|
+
expect(USER_ROLES.ADMIN).toBe("ADMINS");
|
|
107
|
+
expect(USER_ROLES.BRAND_ADMIN).toBe("BRAND_ADMINS");
|
|
108
|
+
expect(USER_ROLES.BRAND_USER).toBe("BRAND_USERS");
|
|
109
|
+
expect(USER_ROLES.USER).toBe("USERS");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should be defined as const", () => {
|
|
113
|
+
expect(USER_ROLES.SUPER_ADMIN).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @htlkg/core/constants
|
|
3
|
+
* Core constants for Hotelinking applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Application routes
|
|
8
|
+
*/
|
|
9
|
+
export const ROUTES = {
|
|
10
|
+
// Admin routes
|
|
11
|
+
ADMIN: {
|
|
12
|
+
HOME: "/admin",
|
|
13
|
+
BRANDS: "/admin/brands",
|
|
14
|
+
ACCOUNTS: "/admin/accounts",
|
|
15
|
+
USERS: "/admin/users",
|
|
16
|
+
PRODUCTS: "/admin/products",
|
|
17
|
+
SETTINGS: "/admin/settings",
|
|
18
|
+
},
|
|
19
|
+
// Brand routes
|
|
20
|
+
BRAND: {
|
|
21
|
+
HOME: (brandId: string) => `/${brandId}`,
|
|
22
|
+
ADMIN: (brandId: string) => `/${brandId}/admin`,
|
|
23
|
+
TEMPLATES: (brandId: string) => `/${brandId}/admin/templates`,
|
|
24
|
+
SETTINGS: (brandId: string) => `/${brandId}/admin/settings`,
|
|
25
|
+
},
|
|
26
|
+
// Auth routes
|
|
27
|
+
AUTH: {
|
|
28
|
+
LOGIN: "/login",
|
|
29
|
+
LOGOUT: "/logout",
|
|
30
|
+
SIGNUP: "/signup",
|
|
31
|
+
FORGOT_PASSWORD: "/forgot-password",
|
|
32
|
+
},
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Product definitions
|
|
37
|
+
*/
|
|
38
|
+
export const PRODUCTS = {
|
|
39
|
+
WIFI_PORTAL: {
|
|
40
|
+
id: "wifi-portal",
|
|
41
|
+
name: "WiFi Portal",
|
|
42
|
+
description: "Captive portal for WiFi authentication",
|
|
43
|
+
},
|
|
44
|
+
WHATSAPP_CRM: {
|
|
45
|
+
id: "whatsapp-crm",
|
|
46
|
+
name: "WhatsApp CRM",
|
|
47
|
+
description: "WhatsApp integration for customer relationship management",
|
|
48
|
+
},
|
|
49
|
+
ANALYTICS: {
|
|
50
|
+
id: "analytics",
|
|
51
|
+
name: "Analytics",
|
|
52
|
+
description: "Analytics and reporting dashboard",
|
|
53
|
+
},
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Permission definitions
|
|
58
|
+
*/
|
|
59
|
+
export const PERMISSIONS = {
|
|
60
|
+
// Brand permissions
|
|
61
|
+
BRAND_VIEW: "brand:view",
|
|
62
|
+
BRAND_EDIT: "brand:edit",
|
|
63
|
+
BRAND_DELETE: "brand:delete",
|
|
64
|
+
BRAND_CREATE: "brand:create",
|
|
65
|
+
|
|
66
|
+
// Account permissions
|
|
67
|
+
ACCOUNT_VIEW: "account:view",
|
|
68
|
+
ACCOUNT_EDIT: "account:edit",
|
|
69
|
+
ACCOUNT_DELETE: "account:delete",
|
|
70
|
+
ACCOUNT_CREATE: "account:create",
|
|
71
|
+
|
|
72
|
+
// User permissions
|
|
73
|
+
USER_VIEW: "user:view",
|
|
74
|
+
USER_EDIT: "user:edit",
|
|
75
|
+
USER_DELETE: "user:delete",
|
|
76
|
+
USER_CREATE: "user:create",
|
|
77
|
+
|
|
78
|
+
// Product permissions
|
|
79
|
+
PRODUCT_VIEW: "product:view",
|
|
80
|
+
PRODUCT_EDIT: "product:edit",
|
|
81
|
+
PRODUCT_ENABLE: "product:enable",
|
|
82
|
+
PRODUCT_DISABLE: "product:disable",
|
|
83
|
+
|
|
84
|
+
// Admin permissions
|
|
85
|
+
ADMIN_ACCESS: "admin:access",
|
|
86
|
+
SUPER_ADMIN_ACCESS: "super_admin:access",
|
|
87
|
+
} as const;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* User role definitions
|
|
91
|
+
*/
|
|
92
|
+
export const USER_ROLES = {
|
|
93
|
+
SUPER_ADMIN: "SUPER_ADMINS",
|
|
94
|
+
ADMIN: "ADMINS",
|
|
95
|
+
BRAND_ADMIN: "BRAND_ADMINS",
|
|
96
|
+
BRAND_USER: "BRAND_USERS",
|
|
97
|
+
USER: "USERS",
|
|
98
|
+
} as const;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property-based tests for @htlkg/core module exports
|
|
3
|
+
* Feature: htlkg-modular-architecture, Property 1: Module exports are accessible
|
|
4
|
+
* Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
|
|
8
|
+
describe("Property 1: Module exports are accessible", () => {
|
|
9
|
+
it("should export all auth functions from main module", async () => {
|
|
10
|
+
// Import from main module
|
|
11
|
+
const mainModule = await import("./index.js");
|
|
12
|
+
expect(mainModule.getUser).toBeDefined();
|
|
13
|
+
expect(mainModule.getClientUser).toBeDefined();
|
|
14
|
+
expect(mainModule.hasAccessToBrand).toBeDefined();
|
|
15
|
+
expect(mainModule.hasAccessToAccount).toBeDefined();
|
|
16
|
+
expect(mainModule.isAdminUser).toBeDefined();
|
|
17
|
+
expect(mainModule.isSuperAdminUser).toBeDefined();
|
|
18
|
+
expect(mainModule.requireAuth).toBeDefined();
|
|
19
|
+
expect(mainModule.requireAdminAccess).toBeDefined();
|
|
20
|
+
expect(mainModule.requireBrandAccess).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should export all auth functions from auth subpath", async () => {
|
|
24
|
+
// Import from auth subpath
|
|
25
|
+
const authModule = await import("./auth/index.js");
|
|
26
|
+
expect(authModule.getUser).toBeDefined();
|
|
27
|
+
expect(authModule.getClientUser).toBeDefined();
|
|
28
|
+
expect(authModule.hasAccessToBrand).toBeDefined();
|
|
29
|
+
expect(authModule.hasAccessToAccount).toBeDefined();
|
|
30
|
+
expect(authModule.isAdminUser).toBeDefined();
|
|
31
|
+
expect(authModule.isSuperAdminUser).toBeDefined();
|
|
32
|
+
expect(authModule.requireAuth).toBeDefined();
|
|
33
|
+
expect(authModule.requireAdminAccess).toBeDefined();
|
|
34
|
+
expect(authModule.requireBrandAccess).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should export all utility functions from main module", async () => {
|
|
38
|
+
const mainModule = await import("./index.js");
|
|
39
|
+
expect(mainModule.formatDate).toBeDefined();
|
|
40
|
+
expect(mainModule.truncateText).toBeDefined();
|
|
41
|
+
expect(mainModule.groupBy).toBeDefined();
|
|
42
|
+
expect(mainModule.debounce).toBeDefined();
|
|
43
|
+
expect(mainModule.throttle).toBeDefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should export all utility functions from utils subpath", async () => {
|
|
47
|
+
const utilsModule = await import("./utils/index.js");
|
|
48
|
+
expect(utilsModule.formatDate).toBeDefined();
|
|
49
|
+
expect(utilsModule.truncateText).toBeDefined();
|
|
50
|
+
expect(utilsModule.groupBy).toBeDefined();
|
|
51
|
+
expect(utilsModule.debounce).toBeDefined();
|
|
52
|
+
expect(utilsModule.throttle).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should export all constants from main module", async () => {
|
|
56
|
+
const mainModule = await import("./index.js");
|
|
57
|
+
expect(mainModule.ROUTES).toBeDefined();
|
|
58
|
+
expect(mainModule.PRODUCTS).toBeDefined();
|
|
59
|
+
expect(mainModule.PERMISSIONS).toBeDefined();
|
|
60
|
+
expect(mainModule.USER_ROLES).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should export all constants from constants subpath", async () => {
|
|
64
|
+
const constantsModule = await import("./constants/index.js");
|
|
65
|
+
expect(constantsModule.ROUTES).toBeDefined();
|
|
66
|
+
expect(constantsModule.PRODUCTS).toBeDefined();
|
|
67
|
+
expect(constantsModule.PERMISSIONS).toBeDefined();
|
|
68
|
+
expect(constantsModule.USER_ROLES).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should export all error classes from main module", async () => {
|
|
72
|
+
const mainModule = await import("./index.js");
|
|
73
|
+
expect(mainModule.AppError).toBeDefined();
|
|
74
|
+
expect(mainModule.AuthError).toBeDefined();
|
|
75
|
+
expect(mainModule.ValidationError).toBeDefined();
|
|
76
|
+
expect(mainModule.NotFoundError).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should export all error classes from errors subpath", async () => {
|
|
80
|
+
const errorsModule = await import("./errors/index.js");
|
|
81
|
+
expect(errorsModule.AppError).toBeDefined();
|
|
82
|
+
expect(errorsModule.AuthError).toBeDefined();
|
|
83
|
+
expect(errorsModule.ValidationError).toBeDefined();
|
|
84
|
+
expect(errorsModule.NotFoundError).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should have working utility functions for various inputs", async () => {
|
|
88
|
+
const { formatDate, truncateText, groupBy } = await import("./index.js");
|
|
89
|
+
|
|
90
|
+
// Test with multiple different inputs to verify the property holds
|
|
91
|
+
const testCases = [
|
|
92
|
+
{
|
|
93
|
+
date: new Date("2024-01-15"),
|
|
94
|
+
text: "Hello World",
|
|
95
|
+
items: [
|
|
96
|
+
{ id: 1, category: "A" },
|
|
97
|
+
{ id: 2, category: "B" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
date: new Date("2023-12-25"),
|
|
102
|
+
text: "Short",
|
|
103
|
+
items: [{ id: 1, category: "A" }],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
date: new Date(),
|
|
107
|
+
text: "A very long text that should be truncated properly",
|
|
108
|
+
items: [],
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const testCase of testCases) {
|
|
113
|
+
// formatDate should not throw
|
|
114
|
+
expect(() => formatDate(testCase.date)).not.toThrow();
|
|
115
|
+
|
|
116
|
+
// truncateText should not throw
|
|
117
|
+
expect(() => truncateText(testCase.text, 50)).not.toThrow();
|
|
118
|
+
|
|
119
|
+
// groupBy should not throw
|
|
120
|
+
expect(() =>
|
|
121
|
+
groupBy(testCase.items, (item) => item.category),
|
|
122
|
+
).not.toThrow();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should have working error classes for various inputs", async () => {
|
|
127
|
+
const { AppError, AuthError, ValidationError, NotFoundError } =
|
|
128
|
+
await import("./index.js");
|
|
129
|
+
|
|
130
|
+
const testCases = [
|
|
131
|
+
{ message: "Error 1", code: "CODE1", statusCode: 400 },
|
|
132
|
+
{ message: "Error 2", code: "CODE2", statusCode: 500 },
|
|
133
|
+
{ message: "Error 3", code: "CODE3", statusCode: 404 },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const testCase of testCases) {
|
|
137
|
+
// All error classes should be instantiable
|
|
138
|
+
const appError = new AppError(
|
|
139
|
+
testCase.message,
|
|
140
|
+
testCase.code,
|
|
141
|
+
testCase.statusCode,
|
|
142
|
+
);
|
|
143
|
+
expect(appError).toBeInstanceOf(Error);
|
|
144
|
+
expect(appError.message).toBe(testCase.message);
|
|
145
|
+
|
|
146
|
+
const authError = new AuthError(
|
|
147
|
+
testCase.message,
|
|
148
|
+
testCase.code,
|
|
149
|
+
testCase.statusCode,
|
|
150
|
+
);
|
|
151
|
+
expect(authError).toBeInstanceOf(Error);
|
|
152
|
+
expect(authError.message).toBe(testCase.message);
|
|
153
|
+
|
|
154
|
+
const validationError = new ValidationError(testCase.message);
|
|
155
|
+
expect(validationError).toBeInstanceOf(Error);
|
|
156
|
+
expect(validationError.message).toBe(testCase.message);
|
|
157
|
+
|
|
158
|
+
const notFoundError = new NotFoundError("Resource", "123");
|
|
159
|
+
expect(notFoundError).toBeInstanceOf(Error);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should have constants that are defined and accessible", async () => {
|
|
164
|
+
const { ROUTES, PRODUCTS, PERMISSIONS, USER_ROLES } = await import(
|
|
165
|
+
"./index.js"
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Constants should be defined and have expected structure
|
|
169
|
+
expect(ROUTES).toBeDefined();
|
|
170
|
+
expect(ROUTES.ADMIN).toBeDefined();
|
|
171
|
+
expect(ROUTES.BRAND).toBeDefined();
|
|
172
|
+
expect(ROUTES.AUTH).toBeDefined();
|
|
173
|
+
|
|
174
|
+
expect(PRODUCTS).toBeDefined();
|
|
175
|
+
expect(PRODUCTS.WIFI_PORTAL).toBeDefined();
|
|
176
|
+
expect(PRODUCTS.WHATSAPP_CRM).toBeDefined();
|
|
177
|
+
|
|
178
|
+
expect(PERMISSIONS).toBeDefined();
|
|
179
|
+
expect(PERMISSIONS.BRAND_VIEW).toBeDefined();
|
|
180
|
+
expect(PERMISSIONS.ADMIN_ACCESS).toBeDefined();
|
|
181
|
+
|
|
182
|
+
expect(USER_ROLES).toBeDefined();
|
|
183
|
+
expect(USER_ROLES.SUPER_ADMIN).toBeDefined();
|
|
184
|
+
expect(USER_ROLES.ADMIN).toBeDefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Errors Module
|
|
2
|
+
|
|
3
|
+
Standardized error classes for consistent error handling across applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
AppError,
|
|
10
|
+
AuthError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
NotFoundError
|
|
13
|
+
} from '@htlkg/core/errors';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Error Classes
|
|
17
|
+
|
|
18
|
+
### AppError
|
|
19
|
+
|
|
20
|
+
Base error class for all application errors.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
class AppError extends Error {
|
|
24
|
+
code: string;
|
|
25
|
+
statusCode: number;
|
|
26
|
+
details?: Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
throw new AppError(
|
|
32
|
+
'Something went wrong',
|
|
33
|
+
'CUSTOM_ERROR', // code
|
|
34
|
+
500, // statusCode
|
|
35
|
+
{ context: 'data' } // details
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### AuthError
|
|
40
|
+
|
|
41
|
+
Authentication and authorization errors.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
class AuthError extends AppError {
|
|
45
|
+
// Defaults: code = 'AUTH_ERROR', statusCode = 401
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
throw new AuthError('Invalid credentials');
|
|
51
|
+
throw new AuthError('Token expired', 'TOKEN_EXPIRED', 401);
|
|
52
|
+
throw new AuthError('Insufficient permissions', 'FORBIDDEN', 403);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### ValidationError
|
|
56
|
+
|
|
57
|
+
Input validation errors with field-level details.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
class ValidationError extends AppError {
|
|
61
|
+
errors: Array<{ field: string; message: string }>;
|
|
62
|
+
// Defaults: code = 'VALIDATION_ERROR', statusCode = 400
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
throw new ValidationError('Validation failed', [
|
|
68
|
+
{ field: 'email', message: 'Invalid email format' },
|
|
69
|
+
{ field: 'password', message: 'Password too short' },
|
|
70
|
+
]);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### NotFoundError
|
|
74
|
+
|
|
75
|
+
Resource not found errors.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
class NotFoundError extends AppError {
|
|
79
|
+
resource: string;
|
|
80
|
+
resourceId?: string;
|
|
81
|
+
// Defaults: code = 'NOT_FOUND', statusCode = 404
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
throw new NotFoundError('Brand', '123');
|
|
87
|
+
// Message: "Brand with id '123' not found"
|
|
88
|
+
|
|
89
|
+
throw new NotFoundError('User');
|
|
90
|
+
// Message: "User not found"
|
|
91
|
+
|
|
92
|
+
throw new NotFoundError('Brand', '123', 'Custom message');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## JSON Serialization
|
|
96
|
+
|
|
97
|
+
All errors support `toJSON()` for API responses:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const error = new ValidationError('Invalid input', [
|
|
101
|
+
{ field: 'email', message: 'Required' }
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
console.log(JSON.stringify(error));
|
|
105
|
+
// {
|
|
106
|
+
// "name": "ValidationError",
|
|
107
|
+
// "message": "Invalid input",
|
|
108
|
+
// "code": "VALIDATION_ERROR",
|
|
109
|
+
// "statusCode": 400,
|
|
110
|
+
// "details": { "errors": [...] },
|
|
111
|
+
// "errors": [{ "field": "email", "message": "Required" }]
|
|
112
|
+
// }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## API Route Usage
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import type { APIContext } from 'astro';
|
|
119
|
+
import { NotFoundError, ValidationError, AuthError } from '@htlkg/core/errors';
|
|
120
|
+
|
|
121
|
+
export async function GET({ params }: APIContext) {
|
|
122
|
+
try {
|
|
123
|
+
const brand = await getBrand(params.id);
|
|
124
|
+
|
|
125
|
+
if (!brand) {
|
|
126
|
+
throw new NotFoundError('Brand', params.id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return new Response(JSON.stringify(brand));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error instanceof AppError) {
|
|
132
|
+
return new Response(JSON.stringify(error.toJSON()), {
|
|
133
|
+
status: error.statusCode,
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Unknown error
|
|
139
|
+
return new Response(JSON.stringify({ error: 'Internal server error' }), {
|
|
140
|
+
status: 500,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Error Handling Middleware
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
function handleError(error: unknown): Response {
|
|
150
|
+
if (error instanceof AuthError) {
|
|
151
|
+
return new Response(JSON.stringify(error.toJSON()), { status: 401 });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (error instanceof ValidationError) {
|
|
155
|
+
return new Response(JSON.stringify({
|
|
156
|
+
message: error.message,
|
|
157
|
+
errors: error.errors,
|
|
158
|
+
}), { status: 400 });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (error instanceof NotFoundError) {
|
|
162
|
+
return new Response(JSON.stringify({
|
|
163
|
+
message: error.message,
|
|
164
|
+
resource: error.resource,
|
|
165
|
+
}), { status: 404 });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (error instanceof AppError) {
|
|
169
|
+
return new Response(JSON.stringify(error.toJSON()), {
|
|
170
|
+
status: error.statusCode
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Fallback
|
|
175
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
176
|
+
}
|
|
177
|
+
```
|