Merge branch 'fix/cache-merging' into staging
This commit is contained in:
@@ -45,6 +45,72 @@ local function legacyDataToBreakdownItem(featureData)
|
||||
}
|
||||
end
|
||||
|
||||
-- Helper function to merge cusProductLegacyData from source into target
|
||||
-- Merges by plan_id: keeps subscription_id from either, merges options arrays
|
||||
-- Mutates targetLegacyData
|
||||
local function mergeCusProductLegacyData(targetLegacyData, sourceLegacyData)
|
||||
if not sourceLegacyData then return end
|
||||
if not targetLegacyData then return end
|
||||
|
||||
local targetCusProduct = targetLegacyData.cusProductLegacyData
|
||||
local sourceCusProduct = sourceLegacyData.cusProductLegacyData
|
||||
|
||||
if not sourceCusProduct then return end
|
||||
|
||||
-- Initialize target cusProductLegacyData if nil
|
||||
if not targetCusProduct then
|
||||
targetLegacyData.cusProductLegacyData = {}
|
||||
targetCusProduct = targetLegacyData.cusProductLegacyData
|
||||
end
|
||||
|
||||
-- Merge each plan_id from source into target
|
||||
for planId, sourceProductData in pairs(sourceCusProduct) do
|
||||
local targetProductData = targetCusProduct[planId]
|
||||
|
||||
if targetProductData then
|
||||
-- Plan exists in target - merge values
|
||||
-- subscription_id: keep target's if exists, otherwise use source's
|
||||
if not targetProductData.subscription_id or targetProductData.subscription_id == cjson.null then
|
||||
targetProductData.subscription_id = sourceProductData.subscription_id
|
||||
end
|
||||
|
||||
-- options: merge by feature_id (add source options that don't exist in target)
|
||||
if sourceProductData.options and #sourceProductData.options > 0 then
|
||||
if not targetProductData.options then
|
||||
targetProductData.options = {}
|
||||
end
|
||||
|
||||
-- Build set of existing feature_ids in target options
|
||||
local existingFeatureIds = {}
|
||||
for _, option in ipairs(targetProductData.options) do
|
||||
if option.feature_id then
|
||||
existingFeatureIds[option.feature_id] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Add source options that don't exist in target
|
||||
for _, sourceOption in ipairs(sourceProductData.options) do
|
||||
if sourceOption.feature_id and not existingFeatureIds[sourceOption.feature_id] then
|
||||
table.insert(targetProductData.options, sourceOption)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Plan doesn't exist in target - add it (deep copy)
|
||||
local newProductData = {
|
||||
subscription_id = sourceProductData.subscription_id,
|
||||
options = {}
|
||||
}
|
||||
if sourceProductData.options then
|
||||
for _, option in ipairs(sourceProductData.options) do
|
||||
table.insert(newProductData.options, option)
|
||||
end
|
||||
end
|
||||
targetCusProduct[planId] = newProductData
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function to merge cusFeatureLegacyData from source into target
|
||||
-- Merges by feature_id: sums prepaid_quantity, merges breakdown_legacy_data by key
|
||||
-- Similar to balance breakdown merging:
|
||||
@@ -257,6 +323,7 @@ local function getCustomerObject(orgId, env, customerId, skipEntityMerge)
|
||||
local entityBase = entityBaseData[entityId]
|
||||
if entityBase and entityBase.legacyData then
|
||||
mergeCusFeatureLegacyData(baseCustomer.legacyData, entityBase.legacyData)
|
||||
mergeCusProductLegacyData(baseCustomer.legacyData, entityBase.legacyData)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -343,6 +410,7 @@ local function getEntityObject(orgId, env, customerId, entityId, skipCustomerMer
|
||||
-- ============================================================================
|
||||
if baseEntity.legacyData and customerBase and customerBase.legacyData then
|
||||
mergeCusFeatureLegacyData(baseEntity.legacyData, customerBase.legacyData)
|
||||
mergeCusProductLegacyData(baseEntity.legacyData, customerBase.legacyData)
|
||||
end
|
||||
else
|
||||
-- No merging - just use entity's own subscriptions
|
||||
|
||||
@@ -739,9 +739,11 @@ local function loadBalances(cacheKey, orgId, env, customerId, entityId)
|
||||
for featureId, entityBalance in pairs(entityBalances) do
|
||||
if not balances[featureId] then
|
||||
-- This balance doesn't exist in customer, add it with zero values
|
||||
-- Copy plan_id from entity so breakdown keys match when merging
|
||||
balances[featureId] = {
|
||||
feature_id = featureId,
|
||||
feature = entityBalance.feature,
|
||||
plan_id = entityBalance.plan_id,
|
||||
unlimited = entityBalance.unlimited,
|
||||
granted_balance = 0,
|
||||
purchased_balance = 0,
|
||||
@@ -750,7 +752,7 @@ local function loadBalances(cacheKey, orgId, env, customerId, entityId)
|
||||
max_purchase = entityBalance.max_purchase or 0,
|
||||
overage_allowed = entityBalance.overage_allowed,
|
||||
reset = entityBalance.reset,
|
||||
breakdown = {}
|
||||
breakdown = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -189,6 +189,35 @@ describe(`${chalk.yellowBright("track-entity-products2: entity product tracking
|
||||
customerFromCache.features[TestFeature.Messages],
|
||||
);
|
||||
|
||||
// EXPECT CACHE TO MATCH DB (CUSTOMER LEVEL)
|
||||
const cacheBalance = customerFromCache.features[TestFeature.Messages];
|
||||
expect(customerFromDb.features[TestFeature.Messages]).toMatchObject({
|
||||
id: cacheBalance.id,
|
||||
balance: cacheBalance.balance,
|
||||
included_usage: cacheBalance.included_usage,
|
||||
interval: cacheBalance.interval,
|
||||
name: cacheBalance.name,
|
||||
next_reset_at: cacheBalance.next_reset_at,
|
||||
overage_allowed: cacheBalance.overage_allowed,
|
||||
type: cacheBalance.type,
|
||||
unlimited: cacheBalance.unlimited,
|
||||
usage: cacheBalance.usage,
|
||||
});
|
||||
|
||||
if (cacheBalance.breakdown) {
|
||||
for (let i = 0; i < cacheBalance.breakdown.length; i++) {
|
||||
const breakdown = cacheBalance.breakdown?.[i];
|
||||
expect(
|
||||
customerFromDb.features[TestFeature.Messages].breakdown?.[i],
|
||||
).toMatchObject({
|
||||
balance: breakdown?.balance,
|
||||
included_usage: breakdown?.included_usage,
|
||||
interval: breakdown?.interval,
|
||||
usage: breakdown?.usage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verify each entity's balance
|
||||
let totalEntityBalanceFromDb = 0;
|
||||
let totalEntityBalanceFromCache = 0;
|
||||
@@ -203,9 +232,38 @@ describe(`${chalk.yellowBright("track-entity-products2: entity product tracking
|
||||
);
|
||||
|
||||
// Each entity should have some messages deducted
|
||||
expect(entityFromDb.features[TestFeature.Messages]).toMatchObject(
|
||||
entityFromCache.features[TestFeature.Messages],
|
||||
);
|
||||
// expect(entityFromDb.features[TestFeature.Messages]).toMatchObject(
|
||||
// entityFromCache.features[TestFeature.Messages],
|
||||
// );
|
||||
// EXPECT DB TO MATCH CACHE (ENTITY LEVEL)
|
||||
expect(entityFromDb.features[TestFeature.Messages]).toMatchObject({
|
||||
balance: entityFromCache.features[TestFeature.Messages].balance,
|
||||
included_usage:
|
||||
entityFromCache.features[TestFeature.Messages].included_usage,
|
||||
interval: entityFromCache.features[TestFeature.Messages].interval,
|
||||
name: entityFromCache.features[TestFeature.Messages].name,
|
||||
type: entityFromCache.features[TestFeature.Messages].type,
|
||||
unlimited: entityFromCache.features[TestFeature.Messages].unlimited,
|
||||
});
|
||||
|
||||
if (entityFromCache.features[TestFeature.Messages].breakdown) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < entityFromCache.features[TestFeature.Messages].breakdown.length;
|
||||
i++
|
||||
) {
|
||||
const breakdown =
|
||||
entityFromCache.features[TestFeature.Messages].breakdown[i];
|
||||
expect(
|
||||
entityFromDb.features[TestFeature.Messages].breakdown?.[i],
|
||||
).toMatchObject({
|
||||
balance: breakdown?.balance,
|
||||
included_usage: breakdown?.included_usage,
|
||||
interval: breakdown?.interval,
|
||||
usage: breakdown?.usage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
totalEntityBalanceFromDb +=
|
||||
entityFromDb.features[TestFeature.Messages].balance;
|
||||
|
||||
Reference in New Issue
Block a user