mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 2002: fix(checkout): resolve itemType validation error for download items
fix(checkout): resolve itemType validation error for download items Updates ItemTypeSchema to accept bitwise flag combinations instead of only individual enum values. The backend returns combined itemType values (e.g., 20480 = ItemPrice | Download) which were causing Zod validation errors when adding download/e-book items to cart. Changes: - Update ItemTypeSchema to use bitwise validation pattern - Add comprehensive unit tests (24 tests) covering individual flags, combinations, and edge cases - Follow same pattern as NotificationChannelSchema and CRUDASchema Closes #5429 Related work items: #5429
This commit is contained in:
committed by
Nino Righi
parent
e1681d8867
commit
cc62441f58
@@ -0,0 +1,148 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ItemType, ItemTypeSchema } from './item-type.schema';
|
||||
|
||||
describe('ItemTypeSchema', () => {
|
||||
describe('Individual flags', () => {
|
||||
it('should accept NotSet (0)', () => {
|
||||
const result = ItemTypeSchema.parse(ItemType.NotSet);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('should accept SingleUnit', () => {
|
||||
const result = ItemTypeSchema.parse(ItemType.SingleUnit);
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
it('should accept Download', () => {
|
||||
const result = ItemTypeSchema.parse(ItemType.Download);
|
||||
expect(result).toBe(4096);
|
||||
});
|
||||
|
||||
it('should accept ItemPrice', () => {
|
||||
const result = ItemTypeSchema.parse(ItemType.ItemPrice);
|
||||
expect(result).toBe(16384);
|
||||
});
|
||||
|
||||
it('should accept all defined item type values', () => {
|
||||
const allTypes = Object.values(ItemType);
|
||||
|
||||
for (const type of allTypes) {
|
||||
expect(() => ItemTypeSchema.parse(type)).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bitwise combined flags', () => {
|
||||
it('should accept Download | ItemPrice (20480)', () => {
|
||||
const combined = ItemType.Download | ItemType.ItemPrice; // 4096 | 16384 = 20480
|
||||
const result = ItemTypeSchema.parse(combined);
|
||||
expect(result).toBe(20480);
|
||||
});
|
||||
|
||||
it('should accept SingleUnit | Download', () => {
|
||||
const combined = ItemType.SingleUnit | ItemType.Download; // 1 | 4096 = 4097
|
||||
const result = ItemTypeSchema.parse(combined);
|
||||
expect(result).toBe(4097);
|
||||
});
|
||||
|
||||
it('should accept Set | Configurable | Discount', () => {
|
||||
const combined = ItemType.Set | ItemType.Configurable | ItemType.Discount; // 4 | 16 | 32 = 52
|
||||
const result = ItemTypeSchema.parse(combined);
|
||||
expect(result).toBe(52);
|
||||
});
|
||||
|
||||
it('should accept multiple combined flags', () => {
|
||||
const combined =
|
||||
ItemType.SingleUnit |
|
||||
ItemType.SalesUnit |
|
||||
ItemType.Download |
|
||||
ItemType.Streaming; // 1 | 2 | 4096 | 8192 = 12291
|
||||
const result = ItemTypeSchema.parse(combined);
|
||||
expect(result).toBe(12291);
|
||||
});
|
||||
|
||||
it('should accept all flags combined', () => {
|
||||
const allFlags = Object.values(ItemType).reduce<number>((a, b) => a | b, 0);
|
||||
const result = ItemTypeSchema.parse(allFlags);
|
||||
expect(result).toBe(allFlags);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid values', () => {
|
||||
it('should reject unknown flag bit (131072)', () => {
|
||||
const unknownFlag = 131072; // Not a valid ItemType flag
|
||||
expect(() => ItemTypeSchema.parse(unknownFlag)).toThrow('Invalid ItemType: contains unknown flags');
|
||||
});
|
||||
|
||||
it('should reject value with unknown bits', () => {
|
||||
const invalidValue = ItemType.Download | 262144; // Valid flag + unknown bit
|
||||
expect(() => ItemTypeSchema.parse(invalidValue)).toThrow('Invalid ItemType: contains unknown flags');
|
||||
});
|
||||
|
||||
it('should reject negative values', () => {
|
||||
expect(() => ItemTypeSchema.parse(-1)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject non-integer values', () => {
|
||||
expect(() => ItemTypeSchema.parse(1.5)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject string values', () => {
|
||||
expect(() => ItemTypeSchema.parse('Download' as any)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject null', () => {
|
||||
expect(() => ItemTypeSchema.parse(null as any)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject undefined', () => {
|
||||
expect(() => ItemTypeSchema.parse(undefined as any)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle 0 (NotSet)', () => {
|
||||
const result = ItemTypeSchema.parse(0);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle maximum valid combined value', () => {
|
||||
// All flags OR'd together
|
||||
const maxValue = Object.values(ItemType).reduce<number>((a, b) => a | b, 0);
|
||||
const result = ItemTypeSchema.parse(maxValue);
|
||||
expect(result).toBe(maxValue);
|
||||
});
|
||||
|
||||
it('should handle SingleUnitItemPrice (16385)', () => {
|
||||
// This is a special case: not a power of 2, but still valid
|
||||
const result = ItemTypeSchema.parse(ItemType.SingleUnitItemPrice);
|
||||
expect(result).toBe(16385);
|
||||
});
|
||||
|
||||
it('should allow SingleUnitItemPrice in combinations', () => {
|
||||
const combined = ItemType.SingleUnitItemPrice | ItemType.Download; // 16385 | 4096 = 20481
|
||||
const result = ItemTypeSchema.parse(combined);
|
||||
expect(result).toBe(20481);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world scenarios', () => {
|
||||
it('should validate download audiobook item type from bug #5429', () => {
|
||||
// This is the actual value that caused the bug
|
||||
const downloadAudiobookType = 20480; // ItemPrice | Download
|
||||
expect(() => ItemTypeSchema.parse(downloadAudiobookType)).not.toThrow();
|
||||
const result = ItemTypeSchema.parse(downloadAudiobookType);
|
||||
expect(result).toBe(20480);
|
||||
});
|
||||
|
||||
it('should validate e-book item type', () => {
|
||||
const ebookType = ItemType.Download | ItemType.SingleUnit;
|
||||
expect(() => ItemTypeSchema.parse(ebookType)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate physical book with various flags', () => {
|
||||
const physicalBook = ItemType.SingleUnit | ItemType.SalesUnit;
|
||||
expect(() => ItemTypeSchema.parse(physicalBook)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,15 @@ export const ItemType = {
|
||||
CustomPrice: 65536,
|
||||
} as const;
|
||||
|
||||
export const ItemTypeSchema = z.nativeEnum(ItemType).describe('Item type');
|
||||
const ALL_FLAGS = Object.values(ItemType).reduce<number>((a, b) => a | b, 0);
|
||||
|
||||
export const ItemTypeSchema = z
|
||||
.number()
|
||||
.int()
|
||||
.nonnegative()
|
||||
.refine((val) => (val & ~ALL_FLAGS) === 0, {
|
||||
message: 'Invalid ItemType: contains unknown flags',
|
||||
})
|
||||
.describe('Item type (bitflags)');
|
||||
|
||||
export type ItemType = z.infer<typeof ItemTypeSchema>;
|
||||
|
||||
@@ -11,11 +11,7 @@ import {
|
||||
ShoppingCartFacade,
|
||||
} from '@isa/checkout/data-access';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
import {
|
||||
ButtonComponent,
|
||||
StatefulButtonComponent,
|
||||
StatefulButtonState,
|
||||
} from '@isa/ui/buttons';
|
||||
import { StatefulButtonComponent, StatefulButtonState } from '@isa/ui/buttons';
|
||||
import { PurchaseOptionsModalService } from '@modal/purchase-options';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
@@ -28,7 +24,7 @@ import { NavigationStateService } from '@isa/core/navigation';
|
||||
templateUrl: './reward-action.component.html',
|
||||
styleUrl: './reward-action.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ButtonComponent, StatefulButtonComponent],
|
||||
imports: [StatefulButtonComponent],
|
||||
})
|
||||
export class RewardActionComponent {
|
||||
#router = inject(Router);
|
||||
|
||||
Reference in New Issue
Block a user