describe("catalogue hierarchy and publication rules",()=>{beforeEach(()=>jest.resetModules()); const setup=(nodes={})=>{const models={Category:{findByPk:jest.fn(async id=>nodes[id]||null)},ProductTranslation:{findOne:jest.fn()},ProductVariant:{findOne:jest.fn()},ProductMedia:{findOne:jest.fn()}};jest.doMock("../../app/models",()=>models);return{service:require("../../app/services/catalogue/catalogue.service"),models};}; test("blocks self-parent",async()=>{const {service}=setup();await expect(service.assertCategoryParent("a","a",{})).rejects.toMatchObject({code:"CATEGORY_SELF_PARENT"});}); test("blocks category cycle",async()=>{const {service}=setup({b:{id:"b",parent_id:"a"}});await expect(service.assertCategoryParent("a","b",{})).rejects.toMatchObject({code:"CATEGORY_CYCLE"});}); test("accepts valid hierarchy",async()=>{const {service}=setup({b:{id:"b",parent_id:null}});await expect(service.assertCategoryParent("a","b",{})).resolves.toBeUndefined();}); test("publish requires English translation, variant, and primary media",async()=>{const {service,models}=setup();models.ProductTranslation.findOne.mockResolvedValue(null);await expect(service.validatePublish({id:"p",slug:"p",brand_id:"b"},{})).rejects.toMatchObject({code:"PUBLISH_REQUIREMENTS_NOT_MET"});models.ProductTranslation.findOne.mockResolvedValue({});models.ProductVariant.findOne.mockResolvedValue({});models.ProductMedia.findOne.mockResolvedValue({});await expect(service.validatePublish({id:"p",slug:"p",brand_id:"b"},{})).resolves.toBeUndefined();}); });