Files
medusa-store/packages/modules/tax/integration-tests/__tests__/index.spec.ts

1088 lines
31 KiB
TypeScript

import { ITaxModuleService } from "@medusajs/types"
import { Module, Modules } from "@medusajs/utils"
import { TaxModuleService } from "@services"
import { moduleIntegrationTestRunner } from "medusa-test-utils"
import { setupTaxStructure } from "../utils/setup-tax-structure"
jest.setTimeout(30000)
moduleIntegrationTestRunner<ITaxModuleService>({
moduleName: Modules.TAX,
testSuite: ({ service }) => {
describe("TaxModuleService", function () {
it(`should export the appropriate linkable configuration`, () => {
const linkable = Module(Modules.TAX, {
service: TaxModuleService,
}).linkable
expect(Object.keys(linkable)).toEqual([
"taxProvider",
"taxRateRule",
"taxRate",
"taxRegion",
])
Object.keys(linkable).forEach((key) => {
delete linkable[key].toJSON
})
expect(linkable).toEqual({
taxProvider: {
id: {
linkable: "tax_provider_id",
primaryKey: "id",
serviceName: "tax",
field: "taxProvider",
},
},
taxRateRule: {
id: {
linkable: "tax_rate_rule_id",
primaryKey: "id",
serviceName: "tax",
field: "taxRateRule",
},
},
taxRate: {
id: {
linkable: "tax_rate_id",
primaryKey: "id",
serviceName: "tax",
field: "taxRate",
},
},
taxRegion: {
id: {
linkable: "tax_region_id",
primaryKey: "id",
serviceName: "tax",
field: "taxRegion",
},
},
})
})
it("should create tax region", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
expect(region).toEqual(
expect.objectContaining({
id: region.id,
country_code: "us",
})
)
})
it("should create two tax regions with the same country code but different province", async () => {
const regionOne = await service.createTaxRegions({
country_code: "US",
province_code: "CA",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const regionTwo = await service.createTaxRegions({
country_code: "US",
province_code: "NY",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
expect(regionOne).toEqual(
expect.objectContaining({
id: regionOne.id,
country_code: "us",
province_code: "ca",
})
)
expect(regionTwo).toEqual(
expect.objectContaining({
id: regionTwo.id,
country_code: "us",
province_code: "ny",
})
)
})
it("should create two tax regions in a child-parent-like relationship", async () => {
const regionOne = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const regionTwo = await service.createTaxRegions({
country_code: "US",
parent_id: regionOne.id,
province_code: "NY",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
expect(regionOne).toEqual(
expect.objectContaining({
id: regionOne.id,
country_code: "us",
province_code: null,
})
)
expect(regionTwo).toEqual(
expect.objectContaining({
id: regionTwo.id,
country_code: "us",
province_code: "ny",
})
)
})
it("should create three tax regions in a child-parent-like relationship", async () => {
const regionOne = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const regionTwo = await service.createTaxRegions({
country_code: "US",
parent_id: regionOne.id,
province_code: "NY",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const regionThree = await service.createTaxRegions({
country_code: "US",
parent_id: regionOne.id,
province_code: "NE",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
expect(regionOne).toEqual(
expect.objectContaining({
id: regionOne.id,
country_code: "us",
province_code: null,
})
)
expect(regionTwo).toEqual(
expect.objectContaining({
id: regionTwo.id,
country_code: "us",
province_code: "ny",
})
)
expect(regionThree).toEqual(
expect.objectContaining({
id: regionThree.id,
country_code: "us",
province_code: "ne",
})
)
})
it("should throw when creating a tax region with a country code of an existing region", async () => {
await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const error = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
}).catch(e => e)
expect(error.message).toEqual("Tax region with country_code: us, already exists.")
})
it("should throw when creating a tax region with a country code and province code of an existing region", async () => {
await service.createTaxRegions({
country_code: "US",
province_code: "CA",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const error = await service.createTaxRegions({
country_code: "US",
province_code: "CA",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
}).catch(e => e)
expect(error.message).toEqual("Tax region with country_code: us, province_code: ca, already exists.")
})
it("should create tax rates and update them", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const rate = await service.createTaxRates({
tax_region_id: region.id,
name: "Shipping Rate",
code: "test",
rate: 8.23,
})
const updatedRate = await service.updateTaxRates(rate.id, {
name: "Updated Rate",
code: "TEST",
rate: 8.25,
})
expect(updatedRate).toEqual(
expect.objectContaining({
tax_region_id: region.id,
rate: 8.25,
name: "Updated Rate",
code: "TEST",
is_default: false,
})
)
const updatedDefaultRate = await service.updateTaxRates(
{ tax_region_id: region.id, is_default: true },
{ rate: 2 }
)
expect(updatedDefaultRate).toEqual([
expect.objectContaining({
tax_region_id: region.id,
rate: 2,
name: "Test Rate",
code: null,
is_default: true,
}),
])
const rates = await service.listTaxRates()
expect(rates).toEqual(
expect.arrayContaining([
expect.objectContaining({
tax_region_id: region.id,
rate: 2,
name: "Test Rate",
is_default: true,
}),
expect.objectContaining({
tax_region_id: region.id,
rate: 8.25,
name: "Updated Rate",
code: "TEST",
is_default: false,
}),
])
)
})
it("should update tax rates with rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const rate = await service.createTaxRates({
tax_region_id: region.id,
name: "Shipping Rate",
code: "test",
rate: 8.23,
})
await service.updateTaxRates(rate.id, {
name: "Updated Rate",
code: "TEST",
rate: 8.25,
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product_type", reference_id: "product_type_id" },
],
})
const rules = await service.listTaxRateRules({ tax_rate_id: rate.id })
expect(rules).toEqual(
expect.arrayContaining([
expect.objectContaining({
reference: "product",
reference_id: "product_id_1",
}),
expect.objectContaining({
reference: "product_type",
reference_id: "product_type_id",
}),
])
)
await service.updateTaxRates(rate.id, {
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product", reference_id: "product_id_2" },
{ reference: "product_type", reference_id: "product_type_id_2" },
{ reference: "product_type", reference_id: "product_type_id_3" },
],
})
const rulesWithDeletes = await service.listTaxRateRules(
{ tax_rate_id: rate.id },
{ withDeleted: true }
)
expect(rulesWithDeletes).toEqual(
expect.arrayContaining([
expect.objectContaining({
reference: "product",
reference_id: "product_id_2",
}),
expect.objectContaining({
reference: "product_type",
reference_id: "product_type_id_2",
}),
expect.objectContaining({
reference: "product_type",
reference_id: "product_type_id_3",
}),
expect.objectContaining({
reference: "product",
reference_id: "product_id_1",
deleted_at: expect.any(Date),
}),
expect.objectContaining({
reference: "product_type",
reference_id: "product_type_id",
deleted_at: expect.any(Date),
}),
])
)
})
it("should create a tax region", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
const [provinceRegion] = await service.createTaxRegions([
{
country_code: "US",
province_code: "CA",
parent_id: region.id,
default_tax_rate: {
name: "CA Rate",
rate: 8.25,
},
},
])
const listedRegions = await service.listTaxRegions()
expect(listedRegions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: region.id,
country_code: "us",
province_code: null,
parent_id: null,
}),
expect.objectContaining({
id: provinceRegion.id,
country_code: "us",
province_code: "ca",
parent_id: region.id,
}),
])
)
const rates = await service.listTaxRates()
expect(rates).toEqual(
expect.arrayContaining([
expect.objectContaining({
tax_region_id: region.id,
rate: 0.2,
name: "Test Rate",
is_default: true,
}),
expect.objectContaining({
tax_region_id: provinceRegion.id,
rate: 8.25,
name: "CA Rate",
is_default: true,
}),
])
)
})
it("should create a tax rate rule", async () => {
const [region] = await service.createTaxRegions([
{
country_code: "US",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
},
])
const rate = await service.createTaxRates({
tax_region_id: region.id,
name: "Shipping Rate",
rate: 8.23,
})
await service.createTaxRateRules([
{
tax_rate_id: rate.id,
reference: "product",
reference_id: "prod_1234",
},
])
const listedRules = await service.listTaxRateRules(
{},
{
relations: ["tax_rate"],
}
)
expect(listedRules).toEqual(
expect.arrayContaining([
expect.objectContaining({
reference: "product",
reference_id: "prod_1234",
tax_rate: expect.objectContaining({
tax_region_id: region.id,
name: "Shipping Rate",
rate: 8.23,
}),
}),
])
)
})
it("applies specific product rules at the province level", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_1", // Matching the specific product rate for CA province
quantity: 1,
}
const calculationContext = {
address: {
country_code: "US",
province_code: "CA",
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 3, // Expecting the reduced rate for specific products in CA
code: "CAREDUCE_PROD",
name: "CA Reduced Rate for Products",
}),
])
})
it("applies specific product type rules at the province level", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_unknown", // This product does not have a specific rule
product_type_id: "product_type_id_1", // Matching the specific product type rate for CA province
quantity: 1,
}
const calculationContext = {
address: {
country_code: "US",
province_code: "CA",
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 1, // Expecting the reduced rate for specific product types in CA
code: "CAREDUCE_TYPE",
name: "CA Reduced Rate for Product Type",
}),
])
})
it("applies specific product type rules at the province level", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_unknown", // This product does not have a specific rule
product_type_id: "product_type_id_1", // Matching the specific product type rate for CA province
quantity: 1,
}
const calculationContext = {
address: {
country_code: "US",
province_code: "CA",
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 1, // Expecting the reduced rate for specific product types in CA
code: "CAREDUCE_TYPE",
name: "CA Reduced Rate for Product Type",
}),
])
})
it("applies default province rules when no specific product or product type rule matches", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_unknown",
quantity: 1,
}
const calculationContext = {
address: {
country_code: "US",
province_code: "NY", // Testing with NY to apply the default provincial rate
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 6, // Expecting the default rate for NY province
code: "NYDEFAULT",
name: "NY Default Rate",
}),
])
})
it("applies specific product rules at the country level when no province rate applies", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_4", // Assuming this ID now has a specific rule at the country level for Canada
quantity: 1,
}
const calculationContext = {
address: {
country_code: "CA",
province_code: "ON", // This province does not have a specific rule
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 3, // Expecting the reduced rate for specific products in Canada
code: "CAREDUCE_PROD_CA",
name: "Canada Reduced Rate for Product",
}),
])
})
it("applies default country rules when no specific product or product type rule matches", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_unknown",
quantity: 1,
}
const calculationContext = {
address: {
country_code: "DE", // Testing with Germany to apply the default country rate
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 19,
code: "DE19",
name: "Germany Default Rate",
}),
])
})
it("prioritizes specific product rules over product type rules", async () => {
await setupTaxStructure(service)
const item = {
id: "item_test",
product_id: "product_id_1", // This product has a specific rule for product type and product
product_type_id: "product_type_id_1", // This product type has a specific rule for product type
quantity: 1,
}
const calculationContext = {
address: {
country_code: "US",
province_code: "CA",
},
}
const taxLines = await service.getTaxLines([item], calculationContext)
expect(taxLines).toEqual([
expect.objectContaining({
rate_id: expect.any(String),
rate: 3, // Expecting the reduced rate for specific products in CA
code: "CAREDUCE_PROD",
name: "CA Reduced Rate for Products",
}),
])
})
it("should delete tax rate", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
const taxRate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})
await service.deleteTaxRates(taxRate.id)
const rates = await service.listTaxRates({ tax_region_id: region.id })
expect(rates).toEqual([])
})
it("should soft delete tax rate", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
const taxRate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})
await service.softDeleteTaxRates([taxRate.id])
const rates = await service.listTaxRates(
{ tax_region_id: region.id },
{ withDeleted: true }
)
expect(rates).toEqual([
expect.objectContaining({
id: taxRate.id,
deleted_at: expect.any(Date),
}),
])
})
it("should delete a tax region and its rates", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
value: 2,
code: "test",
name: "default test",
},
})
await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})
await service.deleteTaxRegions(region.id)
const taxRegions = await service.listTaxRegions()
const rates = await service.listTaxRates()
expect(taxRegions).toEqual([])
expect(rates).toEqual([])
})
it("should soft delete a tax region and its rates", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
value: 2,
code: "test",
name: "default test",
},
})
await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})
await service.softDeleteTaxRegions([region.id])
const taxRegions = await service.listTaxRegions(
{},
{ withDeleted: true }
)
const rates = await service.listTaxRates({}, { withDeleted: true })
expect(taxRegions).toEqual([
expect.objectContaining({
id: region.id,
country_code: "us",
deleted_at: expect.any(Date),
}),
])
expect(rates).toEqual([
expect.objectContaining({
deleted_at: expect.any(Date),
}),
expect.objectContaining({
deleted_at: expect.any(Date),
}),
])
})
it("should delete a tax rate and its rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
const rate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product_type", reference_id: "product_type_id" },
],
})
await service.deleteTaxRates(rate.id)
const taxRegions = await service.listTaxRegions()
const rates = await service.listTaxRates()
const rules = await service.listTaxRateRules()
expect(taxRegions).toEqual([expect.objectContaining({ id: region.id })])
expect(rates).toEqual([])
expect(rules).toEqual([])
})
it("should soft delete a tax rate and its rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
const rate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product_type", reference_id: "product_type_id" },
],
})
await service.softDeleteTaxRates(rate.id)
const taxRegions = await service.listTaxRegions(
{},
{ withDeleted: true }
)
const rates = await service.listTaxRates({}, { withDeleted: true })
const rules = await service.listTaxRateRules({}, { withDeleted: true })
expect(taxRegions).toEqual([
expect.objectContaining({ id: region.id, deleted_at: null }),
])
expect(rates).toEqual([
expect.objectContaining({
id: rate.id,
deleted_at: expect.any(Date),
}),
])
expect(rules).toEqual([
expect.objectContaining({
tax_rate_id: rate.id,
deleted_at: expect.any(Date),
}),
expect.objectContaining({
tax_rate_id: rate.id,
deleted_at: expect.any(Date),
}),
])
})
it("should soft delete a tax rule", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
const rate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})
const [ruleOne, ruleTwo] = await service.createTaxRateRules([
{
tax_rate_id: rate.id,
reference: "product",
reference_id: "product_id_1",
},
{
tax_rate_id: rate.id,
reference: "product_type",
reference_id: "product_type_id",
},
])
await service.softDeleteTaxRateRules([ruleOne.id])
const rules = await service.listTaxRateRules({}, { withDeleted: true })
expect(rules).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: ruleOne.id,
deleted_at: expect.any(Date),
}),
expect.objectContaining({
id: ruleTwo.id,
deleted_at: null,
}),
])
)
const rateWithRules = await service.retrieveTaxRate(rate.id, {
relations: ["rules"],
})
expect(rateWithRules.rules.length).toBe(1)
// should be possible to add the rule back again
await service.createTaxRateRules({
tax_rate_id: rate.id,
reference: ruleOne.reference,
reference_id: ruleOne.reference_id,
})
const rateWithRulesAfterReAdd = await service.retrieveTaxRate(rate.id, {
relations: ["rules"],
})
expect(rateWithRulesAfterReAdd.rules.length).toBe(2)
})
it("should fail on duplicate rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})
await expect(
service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product", reference_id: "product_id_1" },
],
})
).rejects.toThrowError(
/Tax rate rule with tax_rate_id: .*?, reference_id: product_id_1, already exists./
)
const rate = await service.createTaxRates({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [{ reference: "product", reference_id: "product_id_1" }],
})
await expect(
service.createTaxRateRules({
tax_rate_id: rate.id,
reference: "product",
reference_id: "product_id_1",
})
).rejects.toThrowError(
/Tax rate rule with tax_rate_id: .*?, reference_id: product_id_1, already exists./
)
})
it("should fail to create province region belonging to a parent with non-matching country", async () => {
const caRegion = await service.createTaxRegions({
country_code: "CA",
})
await expect(
service.createTaxRegions({
country_code: "US", // This should be CA
parent_id: caRegion.id,
province_code: "QC",
})
).rejects.toThrowError(
`Province region must belong to a parent region with the same country code. You are trying to create a province region with (country: us, province: qc) but parent expects (country: ca)`
)
})
it("should fail duplicate regions", async () => {
await service.createTaxRegions({
country_code: "CA",
})
await service.createTaxRegions({
country_code: "CA",
province_code: "QC",
})
await expect(
service.createTaxRegions({
country_code: "CA",
province_code: "QC",
})
).rejects.toThrowError(
"Tax region with country_code: ca, province_code: qc, already exists."
)
})
it("should fail to create region with non-existing parent", async () => {
await expect(
service.createTaxRegions({
parent_id: "something random",
country_code: "CA",
province_code: "QC",
})
).rejects.toThrowError(
`Province region must belong to a parent region. You are trying to create a province region with (country: ca, province: qc) but parent does not exist`
)
})
it("should fail to create two default tax rates", async () => {
const rate = await service.createTaxRegions({
country_code: "CA",
default_tax_rate: {
name: "Test Rate",
rate: 0.2,
},
})
await expect(
service.createTaxRates({
tax_region_id: rate.id,
name: "Shipping Rate",
rate: 8.23,
is_default: true,
})
).rejects.toThrowError(
/Tax rate with tax_region_id: .*?, already exists./
)
})
it("should delete all child regions when parent region is deleted", async () => {
const region = await service.createTaxRegions({
country_code: "CA",
})
const provinceRegion = await service.createTaxRegions({
parent_id: region.id,
country_code: "CA",
province_code: "QC",
})
await service.deleteTaxRegions(region.id)
const taxRegions = await service.listTaxRegions({
id: provinceRegion.id,
})
expect(taxRegions).toEqual([])
})
it("should soft delete all child regions when parent region is deleted", async () => {
const region = await service.createTaxRegions({
country_code: "CA",
})
const provinceRegion = await service.createTaxRegions({
parent_id: region.id,
country_code: "CA",
province_code: "QC",
})
await service.softDeleteTaxRegions([region.id])
const taxRegions = await service.listTaxRegions(
{ id: provinceRegion.id },
{ withDeleted: true }
)
expect(taxRegions).toEqual([
expect.objectContaining({
id: provinceRegion.id,
deleted_at: expect.any(Date),
}),
])
})
})
},
})