* Add is_active field to translation_settings model * Types * Workflows * Api layer * Tests * Add changeset * Add comment * Hook to create or deactivate translatable entities on startup * Cleanup old code * Configure translatable option for core entities * Validation step and snake case correction * Cleanup * Tests * Comment in PR * Update changeset * Mock DmlEntity.getTranslatableEntities * Move validation to module service layer * Remove validation from remaining workflow * Return object directly * Type improvements * Remove .only from tests * Apply snakeCase * Fix tests * Fix tests * Remove unnecessary map and use set instead * Fix tests * Comments * Include translatable product properties * Avoid race condition in translations tests * Update test
529 lines
17 KiB
TypeScript
529 lines
17 KiB
TypeScript
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|
import { DmlEntity, Modules } from "@medusajs/utils"
|
|
import {
|
|
adminHeaders,
|
|
createAdminUser,
|
|
} from "../../../../helpers/create-admin-user"
|
|
|
|
jest.setTimeout(100000)
|
|
|
|
process.env.MEDUSA_FF_TRANSLATION = "true"
|
|
|
|
medusaIntegrationTestRunner({
|
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
describe("Admin Translation Settings Batch API", () => {
|
|
let mockGetTranslatableEntities: jest.SpyInstance
|
|
|
|
beforeEach(async () => {
|
|
mockGetTranslatableEntities = jest.spyOn(
|
|
DmlEntity,
|
|
"getTranslatableEntities"
|
|
)
|
|
mockGetTranslatableEntities.mockReturnValue([
|
|
{ entity: "ProductVariant", fields: ["title", "material"] },
|
|
{ entity: "ProductCategory", fields: ["name", "description"] },
|
|
{ entity: "ProductCollection", fields: ["title"] },
|
|
])
|
|
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
|
|
|
const appContainer = getContainer()
|
|
|
|
const translationModuleService = appContainer.resolve(
|
|
Modules.TRANSLATION
|
|
)
|
|
await translationModuleService.__hooks
|
|
?.onApplicationStart?.()
|
|
.catch(() => {})
|
|
|
|
// Delete all translation settings to be able to test the create operation
|
|
const settings =
|
|
await translationModuleService.listTranslationSettings()
|
|
await translationModuleService.deleteTranslationSettings(
|
|
settings.map((s) => s.id)
|
|
)
|
|
})
|
|
|
|
afterAll(async () => {
|
|
delete process.env.MEDUSA_FF_TRANSLATION
|
|
mockGetTranslatableEntities.mockRestore()
|
|
})
|
|
|
|
describe("POST /admin/translations/settings/batch", () => {
|
|
describe("create", () => {
|
|
it("should create a single translation setting", async () => {
|
|
const response = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(response.status).toEqual(200)
|
|
expect(response.data.created).toHaveLength(1)
|
|
expect(response.data.created[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
created_at: expect.any(String),
|
|
updated_at: expect.any(String),
|
|
})
|
|
)
|
|
})
|
|
|
|
it("should create multiple translation settings", async () => {
|
|
const response = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_category",
|
|
fields: ["name", "description"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: false,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(response.status).toEqual(200)
|
|
expect(response.data.created).toHaveLength(3)
|
|
expect(response.data.created).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
}),
|
|
expect.objectContaining({
|
|
entity_type: "product_category",
|
|
fields: ["name", "description"],
|
|
is_active: true,
|
|
}),
|
|
expect.objectContaining({
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: false,
|
|
}),
|
|
])
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("update", () => {
|
|
it("should update an existing translation setting", async () => {
|
|
const createResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
const settingId = createResponse.data.created[0].id
|
|
|
|
const updateResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
update: [
|
|
{
|
|
id: settingId,
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(updateResponse.status).toEqual(200)
|
|
expect(updateResponse.data.updated).toHaveLength(1)
|
|
expect(updateResponse.data.updated[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: settingId,
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
})
|
|
)
|
|
})
|
|
|
|
it("should update multiple translation settings", async () => {
|
|
const createResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_category",
|
|
fields: ["name"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
const [settingId1, settingId2] = createResponse.data.created.map(
|
|
(s) => s.id
|
|
)
|
|
|
|
const updateResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
update: [
|
|
{
|
|
id: settingId1,
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
},
|
|
{
|
|
id: settingId2,
|
|
entity_type: "product_category",
|
|
is_active: false,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(updateResponse.status).toEqual(200)
|
|
expect(updateResponse.data.updated).toHaveLength(2)
|
|
expect(updateResponse.data.updated).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
}),
|
|
expect.objectContaining({
|
|
entity_type: "product_category",
|
|
is_active: false,
|
|
}),
|
|
])
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("delete", () => {
|
|
it("should delete a translation setting", async () => {
|
|
const createResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
const settingId = createResponse.data.created[0].id
|
|
|
|
const deleteResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
delete: [settingId],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(deleteResponse.status).toEqual(200)
|
|
expect(deleteResponse.data.deleted).toEqual({
|
|
ids: [settingId],
|
|
object: "translation_settings",
|
|
deleted: true,
|
|
})
|
|
})
|
|
|
|
it("should delete multiple translation settings", async () => {
|
|
const createResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_category",
|
|
fields: ["name"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
const ids = createResponse.data.created.map((s) => s.id)
|
|
|
|
const deleteResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
delete: ids,
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(deleteResponse.status).toEqual(200)
|
|
expect(deleteResponse.data.deleted).toEqual({
|
|
ids: expect.arrayContaining(ids),
|
|
object: "translation_settings",
|
|
deleted: true,
|
|
})
|
|
expect(deleteResponse.data.deleted.ids).toHaveLength(3)
|
|
})
|
|
|
|
it("should handle deleting with empty array", async () => {
|
|
const response = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
delete: [],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(response.status).toEqual(200)
|
|
expect(response.data.deleted).toEqual({
|
|
ids: [],
|
|
object: "translation_settings",
|
|
deleted: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("combined operations", () => {
|
|
it("should handle create, update, and delete in a single batch", async () => {
|
|
const setupResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_category",
|
|
fields: ["name"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
const [settingId1, settingId2] = setupResponse.data.created.map(
|
|
(s) => s.id
|
|
)
|
|
|
|
const batchResponse = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
update: [
|
|
{
|
|
id: settingId1,
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: false,
|
|
},
|
|
],
|
|
delete: [settingId2],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(batchResponse.status).toEqual(200)
|
|
expect(batchResponse.data.created).toHaveLength(1)
|
|
expect(batchResponse.data.updated).toHaveLength(1)
|
|
expect(batchResponse.data.deleted.ids).toContain(settingId2)
|
|
|
|
expect(batchResponse.data.created[0]).toEqual(
|
|
expect.objectContaining({
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
})
|
|
)
|
|
|
|
expect(batchResponse.data.updated[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: settingId1,
|
|
fields: ["title", "material"],
|
|
is_active: false,
|
|
})
|
|
)
|
|
})
|
|
|
|
it("should handle empty batch request", async () => {
|
|
const response = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [],
|
|
update: [],
|
|
delete: [],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(response.status).toEqual(200)
|
|
expect(response.data.created).toEqual([])
|
|
expect(response.data.updated).toEqual([])
|
|
expect(response.data.deleted.ids).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe("validation", () => {
|
|
it("should reject non-translatable entity types", async () => {
|
|
const error = await api
|
|
.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "NonExistentEntity",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
.catch((e) => e)
|
|
|
|
expect(error.response.status).toEqual(400)
|
|
expect(error.response.data.message).toContain(
|
|
"NonExistentEntity is not a translatable entity"
|
|
)
|
|
})
|
|
|
|
it("should reject invalid fields for translatable entities", async () => {
|
|
const error = await api
|
|
.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title", "invalid_field", "another_invalid"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
.catch((e) => e)
|
|
|
|
expect(error.response.status).toEqual(400)
|
|
expect(error.response.data.message).toContain("product_variant")
|
|
expect(error.response.data.message).toContain("invalid_field")
|
|
expect(error.response.data.message).toContain("another_invalid")
|
|
})
|
|
|
|
it("should reject multiple invalid settings in a single batch", async () => {
|
|
const error = await api
|
|
.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "NonExistentEntity",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title", "invalid_field"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
.catch((e) => e)
|
|
|
|
expect(error.response.status).toEqual(400)
|
|
expect(error.response.data.message).toContain(
|
|
"NonExistentEntity is not a translatable entity"
|
|
)
|
|
expect(error.response.data.message).toContain("product_variant")
|
|
expect(error.response.data.message).toContain("invalid_field")
|
|
})
|
|
|
|
it("should accept valid fields for translatable entities", async () => {
|
|
const response = await api.post(
|
|
"/admin/translations/settings/batch",
|
|
{
|
|
create: [
|
|
{
|
|
entity_type: "product_variant",
|
|
fields: ["title", "material"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_category",
|
|
fields: ["name", "description"],
|
|
is_active: true,
|
|
},
|
|
{
|
|
entity_type: "product_collection",
|
|
fields: ["title"],
|
|
is_active: true,
|
|
},
|
|
],
|
|
},
|
|
adminHeaders
|
|
)
|
|
|
|
expect(response.status).toEqual(200)
|
|
expect(response.data.created).toHaveLength(3)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
},
|
|
})
|