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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions hooks/use-event-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useCallback } from "react"
import { useApi } from "@/contexts/api-context"
import { buildEventTargetSubscriptionsPath } from "@/lib/event-target-api"

export function useEventTarget() {
const api = useApi()
Expand Down Expand Up @@ -37,10 +38,8 @@ export function useEventTarget() {
}, [api])

const getEventTargetSubscriptions = useCallback(
async (targetType: string, targetName: string) => {
return api.get(
`/target/${encodeURIComponent(targetType)}/${encodeURIComponent(targetName)}/subscriptions`,
) as Promise<
async (service: string, targetName: string) => {
return api.get(buildEventTargetSubscriptionsPath(service, targetName)) as Promise<
Array<{
bucket: string
id?: string
Expand Down
3 changes: 3 additions & 0 deletions lib/event-target-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function buildEventTargetSubscriptionsPath(service: string, targetName: string) {
return `/target/${encodeURIComponent(`notify_${service}`)}/${encodeURIComponent(targetName)}/subscriptions`
}
64 changes: 64 additions & 0 deletions tests/lib/event-target-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from "node:assert/strict"
import test from "node:test"
import { buildEventTargetSubscriptionsPath } from "../../lib/event-target-api"

const loadApiClient = () => import(new URL("../../lib/api-client.ts", import.meta.url).href)

test("destination subscriptions use notification subsystems in admin requests", async () => {
const { ApiClient } = await loadApiClient()
const requests: string[] = []
const expected = [{ bucket: "photos", id: "uploads", events: ["s3:ObjectCreated:*"] }]
const api = new ApiClient(
{
fetch: async (input: string | Request) => {
requests.push(String(input))
return Response.json(expected)
},
},
{ baseUrl: "https://console.test/rustfs/admin/v3" },
)

for (const service of ["webhook", "kafka", "amqp", "mqtt", "nats", "pulsar", "redis", "mysql", "postgres"]) {
const subscriptions = await api.get(buildEventTargetSubscriptionsPath(service, "primary"))
assert.deepEqual(subscriptions, expected)
assert.equal(requests.at(-1), `https://console.test/rustfs/admin/v3/target/notify_${service}/primary/subscriptions`)
}
})

test("destination subscription requests encode names without creating path or query segments", async () => {
const { ApiClient } = await loadApiClient()
const requests: string[] = []
const api = new ApiClient(
{
fetch: async (input: string | Request) => {
requests.push(String(input))
return Response.json([])
},
},
{ baseUrl: "https://console.test/prefix/rustfs/admin/v3" },
)

assert.deepEqual(await api.get(buildEventTargetSubscriptionsPath("webhook", "目标 /?#%")), [])
assert.deepEqual(requests, [
"https://console.test/prefix/rustfs/admin/v3/target/notify_webhook/%E7%9B%AE%E6%A0%87%20%2F%3F%23%25/subscriptions",
])
})

test("destination subscription request failures remain available to the page retry flow", async () => {
const { ApiClient } = await loadApiClient()
let attempts = 0
const api = new ApiClient(
{
fetch: async () => {
attempts += 1
return attempts === 1 ? Response.json({ message: "Temporary failure" }, { status: 500 }) : Response.json([])
},
},
{ baseUrl: "https://console.test/rustfs/admin/v3" },
)
const path = buildEventTargetSubscriptionsPath("webhook", "primary")

await assert.rejects(api.get(path), { status: 500, message: "Temporary failure" })
assert.deepEqual(await api.get(path), [])
assert.equal(attempts, 2)
})