chore: resolve agent comments

This commit is contained in:
Charlie Lamb
2026-05-25 19:50:22 +01:00
parent ff309d4b01
commit 7ab08c43a6
7 changed files with 187 additions and 15 deletions

View File

@@ -38,12 +38,13 @@ export const handleListEntitiesInternal = createRoute({
eq(entities.deleted, false),
);
const searchCondition = search
const escapedSearch = search?.replace(/[%_\\]/g, "\\$&");
const searchCondition = escapedSearch
? and(
baseConditions,
or(
ilike(entities.id, `%${search}%`),
ilike(entities.name, `%${search}%`),
ilike(entities.id, `%${escapedSearch}%`),
ilike(entities.name, `%${escapedSearch}%`),
),
)
: baseConditions;
@@ -63,7 +64,6 @@ export const handleListEntitiesInternal = createRoute({
return c.json({
list: results as Entity[],
total: results.length,
total_count: countResult[0]?.count ?? 0,
});
},

View File

@@ -0,0 +1,39 @@
import { describe, expect, test } from "bun:test";
const escapeIlikePattern = (input: string): string =>
input.replace(/[%_\\]/g, "\\$&");
describe("escapeIlikePattern", () => {
test("passes through plain text unchanged", () => {
expect(escapeIlikePattern("hello")).toBe("hello");
});
test("escapes % wildcard", () => {
expect(escapeIlikePattern("50%")).toBe("50\\%");
});
test("escapes _ single-char wildcard", () => {
expect(escapeIlikePattern("us_r")).toBe("us\\_r");
});
test("escapes backslash", () => {
expect(escapeIlikePattern("path\\to")).toBe("path\\\\to");
});
test("escapes multiple wildcards in one string", () => {
expect(escapeIlikePattern("a%b_c\\d")).toBe("a\\%b\\_c\\\\d");
});
test("handles empty string", () => {
expect(escapeIlikePattern("")).toBe("");
});
test("leaves normal entity IDs unchanged", () => {
expect(escapeIlikePattern("dispersive-preview")).toBe(
"dispersive-preview",
);
expect(escapeIlikePattern("6a13cf0b9b6845d6edbb4aa4")).toBe(
"6a13cf0b9b6845d6edbb4aa4",
);
});
});

View File

@@ -47,7 +47,8 @@ export const useCusQuery = ({
const entityAlreadyLoaded =
!entityId ||
(currentCustomer as FullCustomer)?.customer_products?.some(
(cp: FullCusProduct) => cp.entity_id === entityId,
(cp: FullCusProduct) =>
cp.entity_id === entityId || cp.internal_entity_id === entityId,
);
const effectiveEntityId = entityAlreadyLoaded ? null : entityId;

View File

@@ -21,7 +21,6 @@ export const useEntitiesQuery = ({
const fetcher = async (): Promise<{
list: Entity[];
total: number;
total_count: number;
}> => {
const params = new URLSearchParams();
@@ -42,7 +41,6 @@ export const useEntitiesQuery = ({
return {
entities: (data?.list ?? []) as Entity[],
total: data?.total ?? 0,
totalCount: data?.total_count ?? 0,
isLoading,
error,

View File

@@ -65,12 +65,14 @@ export const useEntitySelector = () => {
enabled: isVisible,
});
const selectedEntity = entities.find(
(e) => e.id === entityId || e.internal_id === entityId,
);
const selectedEntity =
entities.find((e) => e.id === entityId || e.internal_id === entityId) ??
customerEntities.find((e) => e.id === entityId || e.internal_id === entityId);
const effectiveTotalCount = totalCount ?? customerEntities.length;
const entityTypeText = deriveEntityTypeText({
totalCount: totalCount || customerEntities.length,
totalCount: effectiveTotalCount,
entities: entities.length > 0 ? entities : customerEntities,
features: features ?? [],
});
@@ -79,7 +81,7 @@ export const useEntitySelector = () => {
entities,
selectedEntity,
entityId,
totalCount: totalCount || customerEntities.length,
totalCount: effectiveTotalCount,
entityTypeText,
isLoading,
isVisible,

View File

@@ -26,7 +26,7 @@ function filterBySelectedEntity({
return products.filter(
(product) =>
!product.internal_entity_id ||
(!product.internal_entity_id && !product.entity_id) ||
product.internal_entity_id === selectedEntity.internal_id ||
product.entity_id === selectedEntity.id,
);
@@ -41,8 +41,9 @@ export function useCustomerProductsData() {
);
const { subscriptions, purchases, hasEntityProducts } = useMemo(() => {
const allProducts = filterCustomerProducts({ customer, showExpired: showExpired ?? false });
const filtered = filterBySelectedEntity({
products: filterCustomerProducts({ customer, showExpired: showExpired ?? false }),
products: allProducts,
entityId,
entities: customer.entities,
});
@@ -50,7 +51,7 @@ export function useCustomerProductsData() {
return {
subscriptions: filtered.filter((p) => !isOneOffCusProduct(p)),
purchases: filtered.filter((p) => isOneOffCusProduct(p)),
hasEntityProducts: filtered.some(
hasEntityProducts: allProducts.some(
(p) => p.internal_entity_id || p.entity_id,
),
};

View File

@@ -0,0 +1,131 @@
import { describe, expect, test } from "bun:test";
import type { Entity, FullCusProduct } from "@autumn/shared";
const makeProduct = (overrides: Partial<FullCusProduct> = {}): FullCusProduct =>
({
id: "cp-1",
entity_id: null,
internal_entity_id: null,
...overrides,
}) as unknown as FullCusProduct;
const makeEntity = (overrides: Partial<Entity> = {}): Entity =>
({
id: "ent-1",
internal_id: "int-ent-1",
...overrides,
}) as unknown as Entity;
function filterBySelectedEntity({
products,
entityId,
entities,
}: {
products: FullCusProduct[];
entityId: string | null;
entities: Entity[];
}): FullCusProduct[] {
if (!entityId) return products;
const selectedEntity = entities.find(
(e: Entity) => e.id === entityId || e.internal_id === entityId,
);
if (!selectedEntity) return products;
return products.filter(
(product) =>
(!product.internal_entity_id && !product.entity_id) ||
product.internal_entity_id === selectedEntity.internal_id ||
product.entity_id === selectedEntity.id,
);
}
describe("filterBySelectedEntity", () => {
const entity = makeEntity({ id: "ent-1", internal_id: "int-ent-1" });
const otherEntity = makeEntity({ id: "ent-2", internal_id: "int-ent-2" });
const entities = [entity, otherEntity];
test("returns all products when no entityId", () => {
const products = [
makeProduct({ id: "cp-1" }),
makeProduct({ id: "cp-2", entity_id: "ent-1" }),
];
const result = filterBySelectedEntity({
products,
entityId: null,
entities,
});
expect(result).toHaveLength(2);
});
test("includes customer-level products (no entity)", () => {
const customerProduct = makeProduct({ id: "cp-cus" });
const entityProduct = makeProduct({
id: "cp-ent",
entity_id: "ent-1",
internal_entity_id: "int-ent-1",
});
const result = filterBySelectedEntity({
products: [customerProduct, entityProduct],
entityId: "ent-1",
entities,
});
expect(result).toContainEqual(customerProduct);
expect(result).toContainEqual(entityProduct);
});
test("excludes products from other entities", () => {
const otherEntityProduct = makeProduct({
id: "cp-other",
entity_id: "ent-2",
internal_entity_id: "int-ent-2",
});
const result = filterBySelectedEntity({
products: [otherEntityProduct],
entityId: "ent-1",
entities,
});
expect(result).toHaveLength(0);
});
test("does not leak products with only internal_entity_id set", () => {
const leakyProduct = makeProduct({
id: "cp-leak",
entity_id: null,
internal_entity_id: "int-ent-2",
});
const result = filterBySelectedEntity({
products: [leakyProduct],
entityId: "ent-1",
entities,
});
expect(result).toHaveLength(0);
});
test("matches by internal_id when entity_id not set on product", () => {
const internalOnlyProduct = makeProduct({
id: "cp-internal",
entity_id: null,
internal_entity_id: "int-ent-1",
});
const result = filterBySelectedEntity({
products: [internalOnlyProduct],
entityId: "ent-1",
entities,
});
expect(result).toHaveLength(1);
});
test("returns all products when entityId does not match any entity", () => {
const products = [
makeProduct({ id: "cp-1" }),
makeProduct({ id: "cp-2", entity_id: "ent-1" }),
];
const result = filterBySelectedEntity({
products,
entityId: "nonexistent",
entities,
});
expect(result).toHaveLength(2);
});
});