@taruvi/sdk 1.3.3 → 1.3.4-beta.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/.claude/settings.local.json +19 -0
- package/.kiro/settings/lsp.json +198 -0
- package/MODULE_NAMING_CHANGES.md +81 -0
- package/PARAMETER_NAMING_CHANGES.md +106 -0
- package/README.md +4 -4
- package/USAGE_EXAMPLE.md +86 -0
- package/package.json +10 -9
- package/{dist/client.js → src/client.ts} +59 -39
- package/src/index.ts +54 -0
- package/src/lib/analytics/AnalyticsClient.ts +24 -0
- package/src/lib/analytics/types.ts +8 -0
- package/src/lib/app/AppClient.ts +54 -0
- package/src/lib/app/types.ts +50 -0
- package/{dist/lib/auth/AuthClient.js → src/lib/auth/AuthClient.ts} +106 -70
- package/src/lib/auth/types.ts +111 -0
- package/src/lib/database/DatabaseClient.ts +148 -0
- package/src/lib/database/types.ts +46 -0
- package/src/lib/functions/FunctionsClient.ts +27 -0
- package/src/lib/functions/types.ts +25 -0
- package/src/lib/graphs/GraphClient.ts +106 -0
- package/src/lib/graphs/types.ts +33 -0
- package/src/lib/policy/PolicyClient.ts +79 -0
- package/src/lib/policy/types.ts +40 -0
- package/src/lib/secrets/SecretsClient.ts +75 -0
- package/src/lib/secrets/types.ts +59 -0
- package/src/lib/settings/SettingsClient.ts +14 -0
- package/src/lib/settings/types.ts +9 -0
- package/src/lib/storage/StorageClient.ts +123 -0
- package/src/lib/storage/types.ts +86 -0
- package/src/lib/users/UserClient.ts +55 -0
- package/src/lib/users/types.ts +104 -0
- package/src/lib-internal/errors/ErrorClient.ts +102 -0
- package/src/lib-internal/errors/index.ts +3 -0
- package/src/lib-internal/errors/types.ts +28 -0
- package/src/lib-internal/http/HttpClient.ts +129 -0
- package/{dist/lib-internal/http/types.js → src/lib-internal/http/types.ts} +3 -2
- package/src/lib-internal/routes/AnalyticsRoutes.ts +3 -0
- package/src/lib-internal/routes/AppRoutes.ts +9 -0
- package/src/lib-internal/routes/AuthRoutes.ts +0 -0
- package/src/lib-internal/routes/DatabaseRoutes.ts +9 -0
- package/src/lib-internal/routes/FunctionRoutes.ts +3 -0
- package/src/lib-internal/routes/GraphRoutes.ts +14 -0
- package/src/lib-internal/routes/PolicyRoutes.ts +4 -0
- package/src/lib-internal/routes/SecretsRoutes.ts +5 -0
- package/{dist/lib-internal/routes/SettingsRoutes.js → src/lib-internal/routes/SettingsRoutes.ts} +1 -2
- package/src/lib-internal/routes/StorageRoutes.ts +15 -0
- package/src/lib-internal/routes/UserRoutes.ts +11 -0
- package/src/lib-internal/routes/index.ts +0 -0
- package/{dist/lib-internal/token/TokenClient.js → src/lib-internal/token/TokenClient.ts} +144 -99
- package/src/lib-internal/token/types.ts +0 -0
- package/src/types.ts +90 -0
- package/{dist/utils/enums.js → src/utils/enums.ts} +10 -4
- package/src/utils/utils.ts +37 -0
- package/tests/fixtures/mockClient.ts +19 -0
- package/tests/mocks/db.json +1 -0
- package/tests/unit/analytics/AnalyticsClient.test.ts +84 -0
- package/tests/unit/app/AppClient.test.ts +114 -0
- package/tests/unit/auth/AuthClient.test.ts +131 -0
- package/tests/unit/client/Client.test.ts +70 -0
- package/tests/unit/database/DatabaseClient.test.ts +304 -0
- package/tests/unit/edge-cases/robustness.test.ts +259 -0
- package/tests/unit/errors/errors.test.ts +209 -0
- package/tests/unit/functions/FunctionsClient.test.ts +99 -0
- package/tests/unit/graphs/GraphClient.test.ts +329 -0
- package/tests/unit/policy/PolicyClient.test.ts +184 -0
- package/tests/unit/secrets/SecretsClient.test.ts +146 -0
- package/tests/unit/settings/SettingsClient.test.ts +50 -0
- package/tests/unit/storage/StorageClient.test.ts +251 -0
- package/tests/unit/users/UserClient.test.ts +150 -0
- package/tsconfig.json +43 -0
- package/vitest.config.ts +7 -0
- package/dist/client.d.ts +0 -29
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts +0 -25
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -15
- package/dist/index.js.map +0 -1
- package/dist/lib/Analytics/AnalyticsClient.d.ts +0 -9
- package/dist/lib/Analytics/AnalyticsClient.d.ts.map +0 -1
- package/dist/lib/Analytics/AnalyticsClient.js +0 -17
- package/dist/lib/Analytics/AnalyticsClient.js.map +0 -1
- package/dist/lib/Analytics/types.d.ts +0 -7
- package/dist/lib/Analytics/types.d.ts.map +0 -1
- package/dist/lib/Analytics/types.js +0 -2
- package/dist/lib/Analytics/types.js.map +0 -1
- package/dist/lib/App/AppClient.d.ts +0 -15
- package/dist/lib/App/AppClient.d.ts.map +0 -1
- package/dist/lib/App/AppClient.js +0 -41
- package/dist/lib/App/AppClient.js.map +0 -1
- package/dist/lib/App/types.d.ts +0 -36
- package/dist/lib/App/types.d.ts.map +0 -1
- package/dist/lib/App/types.js +0 -2
- package/dist/lib/App/types.js.map +0 -1
- package/dist/lib/Database/DatabaseClient.d.ts +0 -28
- package/dist/lib/Database/DatabaseClient.d.ts.map +0 -1
- package/dist/lib/Database/DatabaseClient.js +0 -116
- package/dist/lib/Database/DatabaseClient.js.map +0 -1
- package/dist/lib/Database/types.d.ts +0 -24
- package/dist/lib/Database/types.d.ts.map +0 -1
- package/dist/lib/Database/types.js +0 -2
- package/dist/lib/Database/types.js.map +0 -1
- package/dist/lib/Function/FunctionsClient.d.ts +0 -9
- package/dist/lib/Function/FunctionsClient.d.ts.map +0 -1
- package/dist/lib/Function/FunctionsClient.js +0 -20
- package/dist/lib/Function/FunctionsClient.js.map +0 -1
- package/dist/lib/Function/types.d.ts +0 -16
- package/dist/lib/Function/types.d.ts.map +0 -1
- package/dist/lib/Function/types.js +0 -2
- package/dist/lib/Function/types.js.map +0 -1
- package/dist/lib/Graphs/GraphClient.d.ts +0 -26
- package/dist/lib/Graphs/GraphClient.d.ts.map +0 -1
- package/dist/lib/Graphs/GraphClient.js +0 -86
- package/dist/lib/Graphs/GraphClient.js.map +0 -1
- package/dist/lib/Graphs/types.d.ts +0 -23
- package/dist/lib/Graphs/types.d.ts.map +0 -1
- package/dist/lib/Graphs/types.js +0 -2
- package/dist/lib/Graphs/types.js.map +0 -1
- package/dist/lib/Policy/PolicyClient.d.ts +0 -9
- package/dist/lib/Policy/PolicyClient.d.ts.map +0 -1
- package/dist/lib/Policy/PolicyClient.js +0 -24
- package/dist/lib/Policy/PolicyClient.js.map +0 -1
- package/dist/lib/Policy/types.d.ts +0 -22
- package/dist/lib/Policy/types.d.ts.map +0 -1
- package/dist/lib/Policy/types.js +0 -2
- package/dist/lib/Policy/types.js.map +0 -1
- package/dist/lib/Secrets/SecretsClient.d.ts +0 -14
- package/dist/lib/Secrets/SecretsClient.d.ts.map +0 -1
- package/dist/lib/Secrets/SecretsClient.js +0 -32
- package/dist/lib/Secrets/SecretsClient.js.map +0 -1
- package/dist/lib/Secrets/types.d.ts +0 -13
- package/dist/lib/Secrets/types.d.ts.map +0 -1
- package/dist/lib/Secrets/types.js +0 -2
- package/dist/lib/Secrets/types.js.map +0 -1
- package/dist/lib/Settings/SettingsClient.d.ts +0 -7
- package/dist/lib/Settings/SettingsClient.d.ts.map +0 -1
- package/dist/lib/Settings/SettingsClient.js +0 -11
- package/dist/lib/Settings/SettingsClient.js.map +0 -1
- package/dist/lib/Settings/types.d.ts +0 -4
- package/dist/lib/Settings/types.d.ts.map +0 -1
- package/dist/lib/Settings/types.js +0 -2
- package/dist/lib/Settings/types.js.map +0 -1
- package/dist/lib/Storage/StorageClient.d.ts +0 -26
- package/dist/lib/Storage/StorageClient.d.ts.map +0 -1
- package/dist/lib/Storage/StorageClient.js +0 -78
- package/dist/lib/Storage/StorageClient.js.map +0 -1
- package/dist/lib/Storage/types.d.ts +0 -35
- package/dist/lib/Storage/types.d.ts.map +0 -1
- package/dist/lib/Storage/types.js +0 -2
- package/dist/lib/Storage/types.js.map +0 -1
- package/dist/lib/auth/AuthClient.d.ts +0 -66
- package/dist/lib/auth/AuthClient.d.ts.map +0 -1
- package/dist/lib/auth/AuthClient.js.map +0 -1
- package/dist/lib/auth/types.d.ts +0 -9
- package/dist/lib/auth/types.d.ts.map +0 -1
- package/dist/lib/auth/types.js +0 -2
- package/dist/lib/auth/types.js.map +0 -1
- package/dist/lib/user/UserClient.d.ts +0 -16
- package/dist/lib/user/UserClient.d.ts.map +0 -1
- package/dist/lib/user/UserClient.js +0 -41
- package/dist/lib/user/UserClient.js.map +0 -1
- package/dist/lib/user/types.d.ts +0 -100
- package/dist/lib/user/types.d.ts.map +0 -1
- package/dist/lib/user/types.js +0 -2
- package/dist/lib/user/types.js.map +0 -1
- package/dist/lib-internal/errors/ErrorClient.d.ts +0 -4
- package/dist/lib-internal/errors/ErrorClient.d.ts.map +0 -1
- package/dist/lib-internal/errors/ErrorClient.js +0 -6
- package/dist/lib-internal/errors/ErrorClient.js.map +0 -1
- package/dist/lib-internal/errors/index.d.ts +0 -8
- package/dist/lib-internal/errors/index.d.ts.map +0 -1
- package/dist/lib-internal/errors/index.js +0 -23
- package/dist/lib-internal/errors/index.js.map +0 -1
- package/dist/lib-internal/errors/types.d.ts +0 -83
- package/dist/lib-internal/errors/types.d.ts.map +0 -1
- package/dist/lib-internal/errors/types.js +0 -65
- package/dist/lib-internal/errors/types.js.map +0 -1
- package/dist/lib-internal/http/HttpClient.d.ts +0 -22
- package/dist/lib-internal/http/HttpClient.d.ts.map +0 -1
- package/dist/lib-internal/http/HttpClient.js +0 -65
- package/dist/lib-internal/http/HttpClient.js.map +0 -1
- package/dist/lib-internal/http/types.d.ts +0 -12
- package/dist/lib-internal/http/types.d.ts.map +0 -1
- package/dist/lib-internal/http/types.js.map +0 -1
- package/dist/lib-internal/routes/AnalyticsRoutes.d.ts +0 -4
- package/dist/lib-internal/routes/AnalyticsRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/AnalyticsRoutes.js +0 -4
- package/dist/lib-internal/routes/AnalyticsRoutes.js.map +0 -1
- package/dist/lib-internal/routes/AppRoutes.d.ts +0 -10
- package/dist/lib-internal/routes/AppRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/AppRoutes.js +0 -6
- package/dist/lib-internal/routes/AppRoutes.js.map +0 -1
- package/dist/lib-internal/routes/AuthRoutes.d.ts +0 -2
- package/dist/lib-internal/routes/AuthRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/AuthRoutes.js +0 -2
- package/dist/lib-internal/routes/AuthRoutes.js.map +0 -1
- package/dist/lib-internal/routes/DatabaseRoutes.d.ts +0 -10
- package/dist/lib-internal/routes/DatabaseRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/DatabaseRoutes.js +0 -6
- package/dist/lib-internal/routes/DatabaseRoutes.js.map +0 -1
- package/dist/lib-internal/routes/FunctionRoutes.d.ts +0 -4
- package/dist/lib-internal/routes/FunctionRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/FunctionRoutes.js +0 -4
- package/dist/lib-internal/routes/FunctionRoutes.js.map +0 -1
- package/dist/lib-internal/routes/GraphRoutes.d.ts +0 -14
- package/dist/lib-internal/routes/GraphRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/GraphRoutes.js +0 -11
- package/dist/lib-internal/routes/GraphRoutes.js.map +0 -1
- package/dist/lib-internal/routes/PolicyRoutes.d.ts +0 -5
- package/dist/lib-internal/routes/PolicyRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/PolicyRoutes.js +0 -5
- package/dist/lib-internal/routes/PolicyRoutes.js.map +0 -1
- package/dist/lib-internal/routes/SecretsRoutes.d.ts +0 -6
- package/dist/lib-internal/routes/SecretsRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/SecretsRoutes.js +0 -6
- package/dist/lib-internal/routes/SecretsRoutes.js.map +0 -1
- package/dist/lib-internal/routes/SettingsRoutes.d.ts +0 -4
- package/dist/lib-internal/routes/SettingsRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/SettingsRoutes.js.map +0 -1
- package/dist/lib-internal/routes/StorageRoutes.d.ts +0 -11
- package/dist/lib-internal/routes/StorageRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/StorageRoutes.js +0 -8
- package/dist/lib-internal/routes/StorageRoutes.js.map +0 -1
- package/dist/lib-internal/routes/UserRoutes.d.ts +0 -11
- package/dist/lib-internal/routes/UserRoutes.d.ts.map +0 -1
- package/dist/lib-internal/routes/UserRoutes.js +0 -11
- package/dist/lib-internal/routes/UserRoutes.js.map +0 -1
- package/dist/lib-internal/routes/index.d.ts +0 -2
- package/dist/lib-internal/routes/index.d.ts.map +0 -1
- package/dist/lib-internal/routes/index.js +0 -2
- package/dist/lib-internal/routes/index.js.map +0 -1
- package/dist/lib-internal/token/TokenClient.d.ts +0 -71
- package/dist/lib-internal/token/TokenClient.d.ts.map +0 -1
- package/dist/lib-internal/token/TokenClient.js.map +0 -1
- package/dist/lib-internal/token/types.d.ts +0 -2
- package/dist/lib-internal/token/types.d.ts.map +0 -1
- package/dist/lib-internal/token/types.js +0 -2
- package/dist/lib-internal/token/types.js.map +0 -1
- package/dist/types.d.ts +0 -49
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/enums.d.ts +0 -20
- package/dist/utils/enums.d.ts.map +0 -1
- package/dist/utils/enums.js.map +0 -1
- package/dist/utils/utils.d.ts +0 -5
- package/dist/utils/utils.d.ts.map +0 -1
- package/dist/utils/utils.js +0 -32
- package/dist/utils/utils.js.map +0 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { Policy } from '../../../src/lib/policy/PolicyClient.js'
|
|
3
|
+
import { Client } from '../../../src/client.js'
|
|
4
|
+
|
|
5
|
+
const mockHttpClient = {
|
|
6
|
+
post: vi.fn()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const mockClient = {
|
|
10
|
+
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
11
|
+
httpClient: mockHttpClient
|
|
12
|
+
} as unknown as Client
|
|
13
|
+
|
|
14
|
+
describe('Policy', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('checkResource()', () => {
|
|
20
|
+
it('checks permissions for single resource', async () => {
|
|
21
|
+
const response = { results: [{ actions: { read: 'EFFECT_ALLOW' } }] }
|
|
22
|
+
mockHttpClient.post.mockResolvedValue(response)
|
|
23
|
+
|
|
24
|
+
const policy = new Policy(mockClient)
|
|
25
|
+
const result = await policy.checkResource([
|
|
26
|
+
{
|
|
27
|
+
entityType: 'crm',
|
|
28
|
+
tableName: 'accounts',
|
|
29
|
+
recordId: 'record-123',
|
|
30
|
+
attributes: { owner_id: 'user-456' },
|
|
31
|
+
actions: ['read']
|
|
32
|
+
}
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
36
|
+
'api/apps/test-app/check/resources',
|
|
37
|
+
{
|
|
38
|
+
resources: [{
|
|
39
|
+
resource: {
|
|
40
|
+
kind: 'crm:accounts',
|
|
41
|
+
id: 'record-123',
|
|
42
|
+
attr: { owner_id: 'user-456' }
|
|
43
|
+
},
|
|
44
|
+
actions: ['read']
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
expect(result).toEqual(response)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('checks permissions for multiple resources', async () => {
|
|
52
|
+
const response = {
|
|
53
|
+
results: [
|
|
54
|
+
{ actions: { read: 'EFFECT_ALLOW', update: 'EFFECT_ALLOW' } },
|
|
55
|
+
{ actions: { delete: 'EFFECT_DENY' } }
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
mockHttpClient.post.mockResolvedValue(response)
|
|
59
|
+
|
|
60
|
+
const policy = new Policy(mockClient)
|
|
61
|
+
await policy.checkResource([
|
|
62
|
+
{
|
|
63
|
+
entityType: 'crm',
|
|
64
|
+
tableName: 'accounts',
|
|
65
|
+
recordId: 'acc-1',
|
|
66
|
+
attributes: {},
|
|
67
|
+
actions: ['read', 'update']
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
entityType: 'docs',
|
|
71
|
+
tableName: 'documents',
|
|
72
|
+
recordId: 'doc-1',
|
|
73
|
+
attributes: {},
|
|
74
|
+
actions: ['delete']
|
|
75
|
+
}
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
79
|
+
'api/apps/test-app/check/resources',
|
|
80
|
+
{
|
|
81
|
+
resources: [
|
|
82
|
+
{
|
|
83
|
+
resource: { kind: 'crm:accounts', id: 'acc-1', attr: {} },
|
|
84
|
+
actions: ['read', 'update']
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
resource: { kind: 'docs:documents', id: 'doc-1', attr: {} },
|
|
88
|
+
actions: ['delete']
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('handles empty attributes', async () => {
|
|
96
|
+
mockHttpClient.post.mockResolvedValue({ results: [] })
|
|
97
|
+
|
|
98
|
+
const policy = new Policy(mockClient)
|
|
99
|
+
await policy.checkResource([
|
|
100
|
+
{
|
|
101
|
+
entityType: 'crm',
|
|
102
|
+
tableName: 'contacts',
|
|
103
|
+
recordId: 'contact-1',
|
|
104
|
+
attributes: {},
|
|
105
|
+
actions: ['read']
|
|
106
|
+
}
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
const body = mockHttpClient.post.mock.calls[0][1]
|
|
110
|
+
expect(body.resources[0].resource.attr).toEqual({})
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('getAllowedActions()', () => {
|
|
115
|
+
it('returns allowed actions for resource', async () => {
|
|
116
|
+
const response = {
|
|
117
|
+
results: [{
|
|
118
|
+
actions: {
|
|
119
|
+
read: 'EFFECT_ALLOW',
|
|
120
|
+
write: 'EFFECT_ALLOW',
|
|
121
|
+
delete: 'EFFECT_DENY'
|
|
122
|
+
}
|
|
123
|
+
}]
|
|
124
|
+
}
|
|
125
|
+
mockHttpClient.post.mockResolvedValue(response)
|
|
126
|
+
|
|
127
|
+
const policy = new Policy(mockClient)
|
|
128
|
+
const result = await policy.getAllowedActions(
|
|
129
|
+
{ kind: 'datatable:users', id: '123', attr: {} }
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(result).toContain('read')
|
|
133
|
+
expect(result).toContain('write')
|
|
134
|
+
expect(result).not.toContain('delete')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('uses default actions when not specified', async () => {
|
|
138
|
+
mockHttpClient.post.mockResolvedValue({ results: [{ actions: {} }] })
|
|
139
|
+
|
|
140
|
+
const policy = new Policy(mockClient)
|
|
141
|
+
await policy.getAllowedActions({ kind: 'test:table', id: '1', attr: {} })
|
|
142
|
+
|
|
143
|
+
const body = mockHttpClient.post.mock.calls[0][1]
|
|
144
|
+
expect(body.resources[0].actions).toEqual(['read', 'write', 'create', 'update', 'delete'])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('uses custom actions when specified', async () => {
|
|
148
|
+
mockHttpClient.post.mockResolvedValue({ results: [{ actions: {} }] })
|
|
149
|
+
|
|
150
|
+
const policy = new Policy(mockClient)
|
|
151
|
+
await policy.getAllowedActions(
|
|
152
|
+
{ kind: 'test:table', id: '1', attr: {} },
|
|
153
|
+
{ actions: ['read', 'update'] }
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const body = mockHttpClient.post.mock.calls[0][1]
|
|
157
|
+
expect(body.resources[0].actions).toEqual(['read', 'update'])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('returns empty array when no results', async () => {
|
|
161
|
+
mockHttpClient.post.mockResolvedValue({ results: [] })
|
|
162
|
+
|
|
163
|
+
const policy = new Policy(mockClient)
|
|
164
|
+
const result = await policy.getAllowedActions(
|
|
165
|
+
{ kind: 'test:table', id: '1', attr: {} }
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
expect(result).toEqual([])
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('includes principal when provided', async () => {
|
|
172
|
+
mockHttpClient.post.mockResolvedValue({ results: [{ actions: {} }] })
|
|
173
|
+
|
|
174
|
+
const policy = new Policy(mockClient)
|
|
175
|
+
await policy.getAllowedActions(
|
|
176
|
+
{ kind: 'test:table', id: '1', attr: {} },
|
|
177
|
+
{ principal: { id: 'user-1', roles: ['admin'], attr: {} } }
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const body = mockHttpClient.post.mock.calls[0][1]
|
|
181
|
+
expect(body.principal).toEqual({ id: 'user-1', roles: ['admin'], attr: {} })
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { Secrets } from '../../../src/lib/secrets/SecretsClient.js'
|
|
3
|
+
import { Client } from '../../../src/client.js'
|
|
4
|
+
import type { SecretResponse, SecretsListResponse } from '../../../src/lib/secrets/types.js'
|
|
5
|
+
|
|
6
|
+
const mockHttpClient = {
|
|
7
|
+
get: vi.fn(),
|
|
8
|
+
put: vi.fn()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const mockClient = {
|
|
12
|
+
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
13
|
+
httpClient: mockHttpClient
|
|
14
|
+
} as unknown as Client
|
|
15
|
+
|
|
16
|
+
describe('Secrets', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('get()', () => {
|
|
22
|
+
it('gets secret by key', async () => {
|
|
23
|
+
const secretData = { key: 'MY_SECRET', value: 'secret-value' }
|
|
24
|
+
mockHttpClient.get.mockResolvedValue(secretData)
|
|
25
|
+
|
|
26
|
+
const secrets = new Secrets(mockClient)
|
|
27
|
+
const result = await secrets.get('MY_SECRET').execute()
|
|
28
|
+
|
|
29
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith('api/secrets/MY_SECRET/')
|
|
30
|
+
expect(result).toEqual(secretData)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('throws error when key is empty', () => {
|
|
34
|
+
const secrets = new Secrets(mockClient)
|
|
35
|
+
expect(() => secrets.get('')).toThrow('Secret key is required')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('throws error when key is not a string', () => {
|
|
39
|
+
const secrets = new Secrets(mockClient)
|
|
40
|
+
expect(() => secrets.get(123 as any)).toThrow('Secret key is required')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('includes app context in query params', async () => {
|
|
44
|
+
mockHttpClient.get.mockResolvedValue({})
|
|
45
|
+
|
|
46
|
+
const secrets = new Secrets(mockClient)
|
|
47
|
+
await secrets.get('MY_SECRET', { app: 'my-app' }).execute()
|
|
48
|
+
|
|
49
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
50
|
+
expect.stringContaining('app=my-app')
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('includes tags in query params', async () => {
|
|
55
|
+
mockHttpClient.get.mockResolvedValue({})
|
|
56
|
+
|
|
57
|
+
const secrets = new Secrets(mockClient)
|
|
58
|
+
await secrets.get('MY_SECRET', { tags: ['prod', 'api'] }).execute()
|
|
59
|
+
|
|
60
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
61
|
+
expect.stringContaining('tags=prod%2Capi')
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('list()', () => {
|
|
67
|
+
it('lists secrets by keys', async () => {
|
|
68
|
+
const response = { MY_KEY: 'value1', OTHER_KEY: 'value2' }
|
|
69
|
+
mockHttpClient.get.mockResolvedValue(response)
|
|
70
|
+
|
|
71
|
+
const secrets = new Secrets(mockClient)
|
|
72
|
+
const result = await secrets.list(['MY_KEY', 'OTHER_KEY'])
|
|
73
|
+
|
|
74
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
75
|
+
expect.stringContaining('keys=MY_KEY%2COTHER_KEY')
|
|
76
|
+
)
|
|
77
|
+
expect(result).toEqual(response)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('includes app context', async () => {
|
|
81
|
+
mockHttpClient.get.mockResolvedValue({})
|
|
82
|
+
|
|
83
|
+
const secrets = new Secrets(mockClient)
|
|
84
|
+
await secrets.list(['KEY1'], { app: 'my-app' })
|
|
85
|
+
|
|
86
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
87
|
+
expect.stringContaining('app=my-app')
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('includes metadata flag', async () => {
|
|
92
|
+
mockHttpClient.get.mockResolvedValue({})
|
|
93
|
+
|
|
94
|
+
const secrets = new Secrets(mockClient)
|
|
95
|
+
await secrets.list(['KEY1'], { includeMetadata: true })
|
|
96
|
+
|
|
97
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
98
|
+
expect.stringContaining('include_metadata=true')
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('execute()', () => {
|
|
104
|
+
it('executes GET request by default', async () => {
|
|
105
|
+
mockHttpClient.get.mockResolvedValue({ key: 'value' })
|
|
106
|
+
|
|
107
|
+
const secrets = new Secrets(mockClient)
|
|
108
|
+
await secrets.execute()
|
|
109
|
+
|
|
110
|
+
expect(mockHttpClient.get).toHaveBeenCalled()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('response handling', () => {
|
|
115
|
+
it('returns single secret matching SecretResponse type', async () => {
|
|
116
|
+
const mockResponse: SecretResponse = {
|
|
117
|
+
status: 'success',
|
|
118
|
+
message: 'Secret retrieved successfully',
|
|
119
|
+
data: { key: 'MY_SECRET', value: 'secret-value', tags: ['prod'], secret_type: 'string' }
|
|
120
|
+
}
|
|
121
|
+
mockHttpClient.get.mockResolvedValue(mockResponse)
|
|
122
|
+
const result = await new Secrets(mockClient).get('MY_SECRET').execute() as SecretResponse
|
|
123
|
+
expect(result.status).toBe('success')
|
|
124
|
+
expect(result.data.key).toBe('MY_SECRET')
|
|
125
|
+
expect(result.data.value).toBe('secret-value')
|
|
126
|
+
expect(result.data.tags).toContain('prod')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('returns secrets list matching SecretsListResponse type', async () => {
|
|
130
|
+
const mockResponse: SecretsListResponse = {
|
|
131
|
+
status: 'success',
|
|
132
|
+
message: 'Retrieved 2 secret(s)',
|
|
133
|
+
data: [
|
|
134
|
+
{ key: 'KEY1', value: 'val1' },
|
|
135
|
+
{ key: 'KEY2', value: 'val2' }
|
|
136
|
+
],
|
|
137
|
+
total: 2
|
|
138
|
+
}
|
|
139
|
+
mockHttpClient.get.mockResolvedValue(mockResponse)
|
|
140
|
+
const result = await new Secrets(mockClient).list(['KEY1', 'KEY2']) as SecretsListResponse
|
|
141
|
+
expect(result.data).toHaveLength(2)
|
|
142
|
+
expect(result.data[0].key).toBe('KEY1')
|
|
143
|
+
expect(result.total).toBe(2)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { Settings } from '../../../src/lib/settings/SettingsClient.js'
|
|
3
|
+
import { Client } from '../../../src/client.js'
|
|
4
|
+
|
|
5
|
+
const mockHttpClient = {
|
|
6
|
+
get: vi.fn()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const mockClient = {
|
|
10
|
+
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
11
|
+
httpClient: mockHttpClient
|
|
12
|
+
} as unknown as Client
|
|
13
|
+
|
|
14
|
+
describe('Settings', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('get()', () => {
|
|
20
|
+
it('fetches site settings', async () => {
|
|
21
|
+
const settingsData = {
|
|
22
|
+
site_name: 'My Site',
|
|
23
|
+
frontend_url: 'https://mysite.com',
|
|
24
|
+
features: { analytics: true }
|
|
25
|
+
}
|
|
26
|
+
mockHttpClient.get.mockResolvedValue(settingsData)
|
|
27
|
+
|
|
28
|
+
const settings = new Settings(mockClient)
|
|
29
|
+
const result = await settings.get()
|
|
30
|
+
|
|
31
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith('api/settings/metadata/')
|
|
32
|
+
expect(result).toEqual(settingsData)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('supports typed response', async () => {
|
|
36
|
+
interface SiteSettings {
|
|
37
|
+
site_name: string
|
|
38
|
+
frontend_url: string
|
|
39
|
+
}
|
|
40
|
+
const settingsData = { site_name: 'Test', frontend_url: 'https://test.com' }
|
|
41
|
+
mockHttpClient.get.mockResolvedValue(settingsData)
|
|
42
|
+
|
|
43
|
+
const settings = new Settings(mockClient)
|
|
44
|
+
const result = await settings.get<SiteSettings>()
|
|
45
|
+
|
|
46
|
+
expect(result.site_name).toBe('Test')
|
|
47
|
+
expect(result.frontend_url).toBe('https://test.com')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { Storage } from '../../../src/lib/storage/StorageClient.js'
|
|
3
|
+
import { Client } from '../../../src/client.js'
|
|
4
|
+
import type { StorageResponse, StorageListResponse, StorageDeleteBatchResponse } from '../../../src/lib/storage/types.js'
|
|
5
|
+
|
|
6
|
+
const mockHttpClient = {
|
|
7
|
+
get: vi.fn(),
|
|
8
|
+
post: vi.fn(),
|
|
9
|
+
put: vi.fn(),
|
|
10
|
+
delete: vi.fn()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mockClient = {
|
|
14
|
+
getConfig: () => ({ apiKey: 'test-key', appSlug: 'test-app', apiUrl: 'https://api.test.com' }),
|
|
15
|
+
httpClient: mockHttpClient
|
|
16
|
+
} as unknown as Client
|
|
17
|
+
|
|
18
|
+
describe('Storage', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('from()', () => {
|
|
24
|
+
it('returns new Storage instance with bucket name', () => {
|
|
25
|
+
const storage = new Storage(mockClient)
|
|
26
|
+
const result = storage.from('documents')
|
|
27
|
+
expect(result).toBeInstanceOf(Storage)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('filter()', () => {
|
|
32
|
+
it('applies search filter', async () => {
|
|
33
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
34
|
+
await new Storage(mockClient).from('documents').filter({ search: 'invoice' }).execute()
|
|
35
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('search=invoice'))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('applies size filters', async () => {
|
|
39
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
40
|
+
await new Storage(mockClient).from('documents').filter({ size__gte: 1024, size__lte: 10485760 }).execute()
|
|
41
|
+
const url = mockHttpClient.get.mock.calls[0][0]
|
|
42
|
+
expect(url).toContain('size__gte=1024')
|
|
43
|
+
expect(url).toContain('size__lte=10485760')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('applies date filters', async () => {
|
|
47
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
48
|
+
await new Storage(mockClient).from('documents').filter({ created_at__gte: '2024-01-01' }).execute()
|
|
49
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('created_at__gte=2024-01-01'))
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('applies mimetype filter', async () => {
|
|
53
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
54
|
+
await new Storage(mockClient).from('documents').filter({ mimetype: 'application/pdf' }).execute()
|
|
55
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('mimetype=application%2Fpdf'))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('applies visibility filter', async () => {
|
|
59
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
60
|
+
await new Storage(mockClient).from('documents').filter({ visibility: 'public' }).execute()
|
|
61
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('visibility=public'))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('applies pagination filters', async () => {
|
|
65
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
66
|
+
await new Storage(mockClient).from('documents').filter({ page: 1, page_size: 20 }).execute()
|
|
67
|
+
const url = mockHttpClient.get.mock.calls[0][0]
|
|
68
|
+
expect(url).toContain('page=1')
|
|
69
|
+
expect(url).toContain('page_size=20')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('applies ordering filter', async () => {
|
|
73
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
74
|
+
await new Storage(mockClient).from('documents').filter({ ordering: '-created_at' }).execute()
|
|
75
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('ordering=-created_at'))
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('download()', () => {
|
|
80
|
+
it('calls httpClient.get with encoded path', async () => {
|
|
81
|
+
mockHttpClient.get.mockResolvedValue(new Blob())
|
|
82
|
+
await new Storage(mockClient).from('documents').download('path/to/file.pdf').execute()
|
|
83
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(expect.stringContaining('path%2Fto%2Ffile.pdf'))
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('upload()', () => {
|
|
88
|
+
it('calls httpClient.post with FormData', async () => {
|
|
89
|
+
const mockFile = new File(['content'], 'test.pdf', { type: 'application/pdf' })
|
|
90
|
+
mockHttpClient.post.mockResolvedValue({ uploaded_count: 1 })
|
|
91
|
+
|
|
92
|
+
await new Storage(mockClient).from('documents').upload({
|
|
93
|
+
files: [mockFile],
|
|
94
|
+
metadatas: [{ name: 'test' }],
|
|
95
|
+
paths: ['test.pdf']
|
|
96
|
+
}).execute()
|
|
97
|
+
|
|
98
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
99
|
+
expect.stringContaining('/batch-upload/'),
|
|
100
|
+
expect.any(FormData)
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('delete()', () => {
|
|
106
|
+
it('calls httpClient.post with paths array', async () => {
|
|
107
|
+
mockHttpClient.post.mockResolvedValue({ deleted_count: 2 })
|
|
108
|
+
await new Storage(mockClient).from('documents').delete(['file1.pdf', 'file2.pdf']).execute()
|
|
109
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
110
|
+
expect.stringContaining('/batch-delete/'),
|
|
111
|
+
{ paths: ['file1.pdf', 'file2.pdf'] }
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('update()', () => {
|
|
117
|
+
it('calls httpClient.put with path and body', async () => {
|
|
118
|
+
const body = { visibility: 'public', metadata: { category: 'reports' } }
|
|
119
|
+
mockHttpClient.put.mockResolvedValue({ id: 1 })
|
|
120
|
+
await new Storage(mockClient).from('documents').update('file.pdf', body).execute()
|
|
121
|
+
expect(mockHttpClient.put).toHaveBeenCalledWith(
|
|
122
|
+
expect.stringContaining('file.pdf'),
|
|
123
|
+
body
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('execute()', () => {
|
|
129
|
+
it('throws error without bucket name', async () => {
|
|
130
|
+
await expect(new Storage(mockClient).execute()).rejects.toThrow('Bucket is required')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('calls httpClient.get for list query', async () => {
|
|
134
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
135
|
+
await new Storage(mockClient).from('documents').execute()
|
|
136
|
+
expect(mockHttpClient.get).toHaveBeenCalled()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('URL building', () => {
|
|
141
|
+
it('builds correct base URL with app slug and bucket', async () => {
|
|
142
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
143
|
+
await new Storage(mockClient).from('documents').execute()
|
|
144
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith('api/apps/test-app/storage/buckets/documents/objects/')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('builds correct URL for download with encoded path', async () => {
|
|
148
|
+
mockHttpClient.get.mockResolvedValue(new Blob())
|
|
149
|
+
await new Storage(mockClient).from('documents').download('folder/file name.pdf').execute()
|
|
150
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
151
|
+
'api/apps/test-app/storage/buckets/documents/objects/folder%2Ffile%20name.pdf/'
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('builds correct URL for batch upload', async () => {
|
|
156
|
+
const mockFile = new File([''], 'test.pdf')
|
|
157
|
+
mockHttpClient.post.mockResolvedValue({})
|
|
158
|
+
await new Storage(mockClient).from('documents').upload({
|
|
159
|
+
files: [mockFile],
|
|
160
|
+
metadatas: [{}],
|
|
161
|
+
paths: ['test.pdf']
|
|
162
|
+
}).execute()
|
|
163
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
164
|
+
'api/apps/test-app/storage/buckets/documents/objects/batch-upload/',
|
|
165
|
+
expect.any(FormData)
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('builds correct URL for batch delete', async () => {
|
|
170
|
+
mockHttpClient.post.mockResolvedValue({})
|
|
171
|
+
await new Storage(mockClient).from('documents').delete(['file.pdf']).execute()
|
|
172
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
173
|
+
'api/apps/test-app/storage/buckets/documents/objects/batch-delete/',
|
|
174
|
+
{ paths: ['file.pdf'] }
|
|
175
|
+
)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('appends query string with filters', async () => {
|
|
179
|
+
mockHttpClient.get.mockResolvedValue([])
|
|
180
|
+
await new Storage(mockClient).from('documents').filter({
|
|
181
|
+
search: 'test',
|
|
182
|
+
page: 1,
|
|
183
|
+
ordering: '-created_at'
|
|
184
|
+
}).execute()
|
|
185
|
+
const url = mockHttpClient.get.mock.calls[0][0]
|
|
186
|
+
expect(url).toContain('?')
|
|
187
|
+
expect(url).toContain('search=test')
|
|
188
|
+
expect(url).toContain('page=1')
|
|
189
|
+
expect(url).toContain('ordering=-created_at')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('response handling', () => {
|
|
194
|
+
it('returns list response matching StorageListResponse type', async () => {
|
|
195
|
+
const mockResponse: StorageListResponse = {
|
|
196
|
+
status: 'success',
|
|
197
|
+
message: 'Objects retrieved successfully',
|
|
198
|
+
data: [{ id: 1, file: 'doc.pdf', path: 'doc.pdf', size: 1024, mimetype: 'application/pdf', created_at: '2024-01-01', updated_at: '2024-01-01' }],
|
|
199
|
+
total: 1
|
|
200
|
+
}
|
|
201
|
+
mockHttpClient.get.mockResolvedValue(mockResponse)
|
|
202
|
+
const result = await new Storage(mockClient).from('documents').execute() as StorageListResponse
|
|
203
|
+
expect(result.status).toBe('success')
|
|
204
|
+
expect(result.data).toHaveLength(1)
|
|
205
|
+
expect(result.data[0].mimetype).toBe('application/pdf')
|
|
206
|
+
expect(result.total).toBe(1)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('returns upload response matching StorageResponse type', async () => {
|
|
210
|
+
const mockResponse: StorageResponse = {
|
|
211
|
+
status: 'success',
|
|
212
|
+
message: 'Object created successfully',
|
|
213
|
+
data: { id: 1, file: 'doc.pdf', path: 'doc.pdf', size: 2048, mimetype: 'application/pdf', created_at: '2024-01-01', updated_at: '2024-01-01' }
|
|
214
|
+
}
|
|
215
|
+
mockHttpClient.post.mockResolvedValue(mockResponse)
|
|
216
|
+
const result = await new Storage(mockClient).from('documents').upload({ files: [], metadatas: [], paths: ['doc.pdf'] }).execute() as StorageResponse
|
|
217
|
+
expect(result.data.file).toBe('doc.pdf')
|
|
218
|
+
expect(result.data.size).toBe(2048)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('returns download response as blob', async () => {
|
|
222
|
+
const mockBlob = new Blob(['content'])
|
|
223
|
+
mockHttpClient.get.mockResolvedValue(mockBlob)
|
|
224
|
+
const result = await new Storage(mockClient).from('documents').download('doc.pdf').execute()
|
|
225
|
+
expect(result).toBeInstanceOf(Blob)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('returns delete response matching StorageDeleteBatchResponse type', async () => {
|
|
229
|
+
const mockResponse: StorageDeleteBatchResponse = {
|
|
230
|
+
status: 'success',
|
|
231
|
+
message: 'Objects deleted successfully',
|
|
232
|
+
data: { deleted_count: 1, failed: [] }
|
|
233
|
+
}
|
|
234
|
+
mockHttpClient.post.mockResolvedValue(mockResponse)
|
|
235
|
+
const result = await new Storage(mockClient).from('documents').delete(['doc.pdf']).execute() as StorageDeleteBatchResponse
|
|
236
|
+
expect(result.status).toBe('success')
|
|
237
|
+
expect(result.data.deleted_count).toBe(1)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('returns update metadata response', async () => {
|
|
241
|
+
const mockResponse: StorageResponse = {
|
|
242
|
+
status: 'success',
|
|
243
|
+
message: 'Object metadata updated successfully',
|
|
244
|
+
data: { id: 1, file: 'doc.pdf', path: 'doc.pdf', size: 1024, mimetype: 'application/pdf', visibility: 'public', created_at: '2024-01-01', updated_at: '2024-01-01' }
|
|
245
|
+
}
|
|
246
|
+
mockHttpClient.put.mockResolvedValue(mockResponse)
|
|
247
|
+
const result = await new Storage(mockClient).from('documents').update('doc.pdf', { visibility: 'public' }).execute() as StorageResponse
|
|
248
|
+
expect(result.data.visibility).toBe('public')
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
})
|