> {
+ const results = await Promise.all(
+ bucketNames.map(async (name) => {
+ try {
+ const response = (await getBucketPolicyStatus(name)) as BucketPolicyStatusResponse
+ return [name, response.PolicyStatus?.IsPublic] as const
+ } catch {
+ return [name, undefined] as const
+ }
+ }),
+ )
+
+ return Object.fromEntries(results)
+}
diff --git a/tests/lib/bucket-access.test.ts b/tests/lib/bucket-access.test.ts
new file mode 100644
index 00000000..61e91e81
--- /dev/null
+++ b/tests/lib/bucket-access.test.ts
@@ -0,0 +1,17 @@
+import test from "node:test"
+import assert from "node:assert/strict"
+
+import { isMissingBucketError } from "../../lib/bucket-access"
+
+test("isMissingBucketError redirects only for a definitive missing bucket response", () => {
+ assert.equal(isMissingBucketError({ $metadata: { httpStatusCode: 404 } }), true)
+ assert.equal(isMissingBucketError({ Code: "NoSuchBucket" }), true)
+})
+
+test("isMissingBucketError does not treat access denied as a missing bucket", () => {
+ assert.equal(isMissingBucketError({ $metadata: { httpStatusCode: 403 }, Code: "AccessDenied" }), false)
+})
+
+test("isMissingBucketError keeps the current bucket when availability is uncertain", () => {
+ assert.equal(isMissingBucketError(new Error("Network request failed")), false)
+})
diff --git a/tests/lib/bucket-policy-status.test.ts b/tests/lib/bucket-policy-status.test.ts
new file mode 100644
index 00000000..b02189f7
--- /dev/null
+++ b/tests/lib/bucket-policy-status.test.ts
@@ -0,0 +1,27 @@
+import test from "node:test"
+import assert from "node:assert/strict"
+
+import { loadBucketPolicyStatuses } from "../../lib/bucket-policy-status"
+
+test("loadBucketPolicyStatuses preserves public and private responses", async () => {
+ const statuses = await loadBucketPolicyStatuses(["public", "private"], async (bucketName) => ({
+ PolicyStatus: { IsPublic: bucketName === "public" },
+ }))
+
+ assert.deepEqual(statuses, {
+ public: true,
+ private: false,
+ })
+})
+
+test("loadBucketPolicyStatuses leaves access-denied responses unknown", async () => {
+ const statuses = await loadBucketPolicyStatuses(["allowed", "denied"], async (bucketName) => {
+ if (bucketName === "denied") throw new Error("AccessDenied")
+ return { PolicyStatus: { IsPublic: false } }
+ })
+
+ assert.deepEqual(statuses, {
+ allowed: false,
+ denied: undefined,
+ })
+})
diff --git a/tests/lib/data-table-error-source.test.js b/tests/lib/data-table-error-source.test.js
new file mode 100644
index 00000000..39a6ef76
--- /dev/null
+++ b/tests/lib/data-table-error-source.test.js
@@ -0,0 +1,12 @@
+import test from "node:test"
+import assert from "node:assert/strict"
+import fs from "node:fs"
+
+test("DataTable renders request failures as an announced error state before empty content", () => {
+ const source = fs.readFileSync("components/data-table/data-table.tsx", "utf8")
+
+ assert.match(source, /errorTitle\?: string/)
+ assert.match(source, /\) : errorTitle \? \(/)
+ assert.match(source, //)
+ assert.match(source, / {
test("object deletion stays blocked until versioning state is known", () => {
assert.match(objectListSource, /setBucketVersioningState\("unknown"\)/)
- assert.match(objectListSource, /versioningError/)
assert.match(objectListSource, /bucketVersioningState === "unknown"/)
- assert.match(objectListSource, /role=\{versioningError \? "alert" : "status"\}/)
+ assert.doesNotMatch(objectListSource, /object-versioning-status/)
assert.doesNotMatch(objectListSource, /catch\s*\{[\s\S]{0,120}setBucketVersioningState\("disabled"\)/)
})
diff --git a/tests/lib/object-list-source.test.js b/tests/lib/object-list-source.test.js
index 886a3371..7c117d47 100644
--- a/tests/lib/object-list-source.test.js
+++ b/tests/lib/object-list-source.test.js
@@ -10,12 +10,16 @@ test("object list normalizes LastModified through the safe date helper", () => {
assert.equal(source.includes('item.LastModified ? item.LastModified.toISOString() : ""'), false)
})
-test("object list falls back to an empty table instead of crashing the page on fetch errors", () => {
+test("object list distinguishes access errors from a confirmed empty bucket", () => {
const source = fs.readFileSync("components/object/list.tsx", "utf8")
assert.equal(source.includes('console.error("Failed to fetch objects:", error)'), true)
assert.equal(source.includes('message.error((error as Error)?.message ?? t("Failed to load objects"))'), true)
assert.equal(source.includes("setData([])"), true)
+ assert.equal(source.includes('setListError(isAccessDeniedError(error) ? "accessDenied" : "loadFailed")'), true)
+ assert.equal(source.includes('listError === "accessDenied"'), true)
+ assert.equal(source.includes('t("Access Denied")'), true)
+ assert.equal(source.includes("{!listError ? ("), true)
})
test("object list lazy loads additional object batches instead of showing a paginator", () => {