chore(): Add new regression tests to the remote joiner (#14119)

* Add tests

* Add tests

* Add tests
This commit is contained in:
Adrien de Peretti
2025-11-27 09:31:25 +01:00
committed by GitHub
parent 2cc42ca0ef
commit 6057afdfaa
3 changed files with 244 additions and 2 deletions

View File

@@ -186,4 +186,62 @@ export const remoteJoinerData = {
user_id: 1,
},
],
link: [
{
id: 1,
url: "https://example.com/post-1",
product_id: 101,
post_id: 501,
metadata: {
source: "blog",
category: "tech",
},
},
{
id: 2,
url: "https://example.com/post-2",
product_id: 102,
post_id: 502,
metadata: {
source: "news",
category: "general",
},
},
{
id: 3,
url: "https://example.com/post-3",
product_id: 103,
post_id: 503,
metadata: {
source: "forum",
category: "discussion",
},
},
],
post: [
{
id: 501,
title: "First Post",
content: "Content of first post",
author: "John Doe",
published: true,
views: 1000,
},
{
id: 502,
title: "Second Post",
content: "Content of second post",
author: "Jane Smith",
published: true,
views: 2500,
},
{
id: 503,
title: "Third Post",
content: "Content of third post",
author: "Bob Johnson",
published: false,
views: 150,
},
],
}

View File

@@ -1,7 +1,7 @@
import { JoinerServiceConfig } from "@medusajs/types"
import { JoinerServiceConfig, ModuleJoinerConfig } from "@medusajs/types"
import { remoteJoinerData } from "./../../__fixtures__/joiner/data"
export const serviceConfigs: JoinerServiceConfig[] = [
export const serviceConfigs: (JoinerServiceConfig | ModuleJoinerConfig)[] = [
{
serviceName: "user",
primaryKeys: ["id"],
@@ -49,6 +49,13 @@ export const serviceConfigs: JoinerServiceConfig[] = [
primaryKey: "id",
alias: "user",
},
{
foreignKey: "product_id",
primaryKey: "id",
serviceName: "link",
alias: "links",
inverse: true,
},
],
},
{
@@ -106,6 +113,74 @@ export const serviceConfigs: JoinerServiceConfig[] = [
},
],
},
{
serviceName: "link",
isLink: true,
primaryKeys: ["id", "product_id", "post_id"],
relationships: [
{
serviceName: "product",
entity: "Product",
primaryKey: "id",
foreignKey: "product_id",
alias: "product",
args: {
methodSuffix: "Products",
},
},
{
serviceName: "post",
entity: "Post",
primaryKey: "id",
foreignKey: "post_id",
alias: "post",
args: {
methodSuffix: "Posts",
},
},
],
extends: [
{
serviceName: "product",
entity: "Product",
fieldAlias: {
posts: "links.post",
},
relationship: {
serviceName: "link",
primaryKey: "id",
foreignKey: "product_id",
alias: "links",
},
},
{
serviceName: "post",
entity: "Post",
fieldAlias: {
product: "links.product",
},
relationship: {
serviceName: "link",
primaryKey: "id",
foreignKey: "post_id",
alias: "links",
},
},
],
},
{
serviceName: "post",
primaryKeys: ["id"],
relationships: [
{
serviceName: "link",
primaryKey: "id",
foreignKey: "post_id",
alias: "links",
inverse: true,
},
],
},
]
export const mockServiceList = (serviceName) => {
@@ -115,6 +190,8 @@ export const mockServiceList = (serviceName) => {
productService: remoteJoinerData.product,
variantService: remoteJoinerData.variant,
orderService: remoteJoinerData.order,
linkService: remoteJoinerData.link,
postService: remoteJoinerData.post,
}
let resultset = JSON.parse(JSON.stringify(src[serviceName]))
@@ -134,6 +211,18 @@ export const mockServiceList = (serviceName) => {
resultset = resultset.filter((item) => data.options.id.includes(item.id))
}
// mock filtering on service link
if (serviceName === "linkService" && data.options?.product_id) {
resultset = resultset.filter((item) =>
data.options.product_id.includes(item.product_id)
)
}
// mock filtering on service post
if (serviceName === "postService" && data.options?.id) {
resultset = resultset.filter((item) => data.options.id.includes(item.id))
}
return {
data: resultset,
path: serviceName === "productService" ? "rows" : undefined,
@@ -146,4 +235,6 @@ export const serviceMock = {
userService: mockServiceList("userService"),
productService: mockServiceList("productService"),
variantService: mockServiceList("variantService"),
linkService: mockServiceList("linkService"),
postService: mockServiceList("postService"),
}

View File

@@ -13,6 +13,9 @@ const container = {
list: (...args) => {
return serviceMock[serviceName].apply(this, args)
},
getByProductId: (...args) => {
return serviceMock[serviceName].apply(this, args)
},
}
},
} as MedusaContainer
@@ -610,4 +613,94 @@ describe("RemoteJoiner", () => {
options: { id: expect.arrayContaining([103, 102]) },
})
})
it("should not lose fields when querying with specific nested fields and wildcard on deeply nested relations", async () => {
// This ensures that when we have:
// - A specific field from the root entity (product.name)
// - A specific field from an intermediate relation (links.metadata)
// - A wildcard on a relation accessed through that intermediate (links.post.*)
// ...the intermediate field (links.metadata) is not lost
const query = {
alias: "product",
fields: ["id", "name"],
expands: [
{
property: "links",
fields: ["metadata"],
},
{
property: "posts",
fields: ["*"],
},
],
args: undefined,
}
const result = await joiner.query(query)
expect(serviceMock.productService).toHaveBeenCalledTimes(1)
expect(serviceMock.productService).toHaveBeenCalledWith({
args: undefined,
fields: expect.arrayContaining(["id", "name"]),
expands: undefined,
options: { id: undefined },
})
expect(serviceMock.linkService).toHaveBeenCalledTimes(1)
expect(serviceMock.linkService).toHaveBeenCalledWith({
args: undefined,
expands: undefined,
fields: expect.arrayContaining(["metadata", "product_id"]),
options: { product_id: expect.arrayContaining([101, 102, 103]) },
})
expect(serviceMock.postService).toHaveBeenCalledTimes(1)
expect(serviceMock.postService).toHaveBeenCalledWith({
args: undefined,
expands: undefined,
fields: ["*", "id"],
options: { id: [501, 502, 503] }, // All posts are fetched
})
expect(result.rows).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 101,
name: "Product 1",
links: expect.objectContaining({
metadata: expect.objectContaining({
source: expect.any(String),
category: expect.any(String),
}),
}),
posts: expect.objectContaining({
id: 501,
title: expect.any(String),
content: expect.any(String),
author: expect.any(String),
published: expect.any(Boolean),
views: expect.any(Number),
}),
}),
])
)
// Critical assertion: metadata must not be lost
const firstProduct = result.rows[0]
expect(firstProduct.links).toBeDefined()
expect(firstProduct.links).toHaveProperty("metadata")
expect(firstProduct.links.metadata).toEqual({
source: "blog",
category: "tech",
})
// posts.* should include all fields
expect(firstProduct).toHaveProperty("posts")
expect(firstProduct.posts).toHaveProperty("id")
expect(firstProduct.posts).toHaveProperty("title")
expect(firstProduct.posts).toHaveProperty("content")
expect(firstProduct.posts).toHaveProperty("author")
expect(firstProduct.posts).toHaveProperty("published")
expect(firstProduct.posts).toHaveProperty("views")
})
})