Just a whole lot of crap

This commit is contained in:
2025-08-14 16:36:12 -05:00
parent 66b7e42275
commit 62f6d6f279
144 changed files with 41421 additions and 2458 deletions

View File

@@ -0,0 +1,328 @@
const TagAnalysisService = require("../../src/services/tagAnalysis");
const ProductService = require("../../src/services/product");
const ProgressService = require("../../src/services/progress");
// Mock the dependencies
jest.mock("../../src/services/product");
jest.mock("../../src/services/progress");
describe("TagAnalysisService", () => {
let tagAnalysisService;
let mockProductService;
let mockProgressService;
const mockProducts = [
{
id: "product1",
title: "Test Product 1",
tags: ["sale", "featured", "new"],
variants: [
{ id: "variant1", price: "29.99" },
{ id: "variant2", price: "39.99" },
],
},
{
id: "product2",
title: "Test Product 2",
tags: ["sale", "clearance"],
variants: [{ id: "variant3", price: "19.99" }],
},
{
id: "product3",
title: "Test Product 3",
tags: ["featured", "premium"],
variants: [
{ id: "variant4", price: "99.99" },
{ id: "variant5", price: "149.99" },
],
},
{
id: "product4",
title: "Test Product 4",
tags: ["new"],
variants: [{ id: "variant6", price: "49.99" }],
},
];
beforeEach(() => {
jest.clearAllMocks();
mockProductService = {
debugFetchAllProductTags: jest.fn(),
fetchProductsByTag: jest.fn(),
};
mockProgressService = {
info: jest.fn().mockResolvedValue(),
error: jest.fn().mockResolvedValue(),
};
ProductService.mockImplementation(() => mockProductService);
ProgressService.mockImplementation(() => mockProgressService);
tagAnalysisService = new TagAnalysisService();
});
describe("getTagAnalysis", () => {
test("successfully analyzes product tags", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
const result = await tagAnalysisService.getTagAnalysis(250);
expect(result).toHaveProperty("totalProducts", 4);
expect(result).toHaveProperty("tagCounts");
expect(result).toHaveProperty("priceRanges");
expect(result).toHaveProperty("recommendations");
expect(result).toHaveProperty("analyzedAt");
// Verify tag counts are sorted by count (descending)
expect(result.tagCounts[0].tag).toBe("sale"); // appears in 2 products
expect(result.tagCounts[0].count).toBe(2);
expect(result.tagCounts[0].percentage).toBe(50.0);
expect(result.tagCounts[1].tag).toBe("featured"); // appears in 2 products
expect(result.tagCounts[1].count).toBe(2);
expect(result.tagCounts[1].percentage).toBe(50.0);
});
test("calculates price ranges correctly", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
const result = await tagAnalysisService.getTagAnalysis();
// Check sale tag price range (products 1 and 2)
const salePriceRange = result.priceRanges["sale"];
expect(salePriceRange).toBeDefined();
expect(salePriceRange.min).toBe(19.99);
expect(salePriceRange.max).toBe(39.99);
expect(salePriceRange.count).toBe(3); // 2 variants from product1 + 1 from product2
expect(salePriceRange.average).toBeCloseTo(29.99, 2); // (29.99 + 39.99 + 19.99) / 3
// Check featured tag price range (products 1 and 3)
const featuredPriceRange = result.priceRanges["featured"];
expect(featuredPriceRange).toBeDefined();
expect(featuredPriceRange.min).toBe(29.99);
expect(featuredPriceRange.max).toBe(149.99);
expect(featuredPriceRange.count).toBe(4); // 2 from product1 + 2 from product3
});
test("generates appropriate recommendations", async () => {
// Create more products to meet the minimum count requirement for caution tags
const moreProducts = [
...mockProducts,
{
id: "product5",
title: "Product 5",
tags: ["sale"],
variants: [{ id: "v5", price: "25.99" }],
},
{
id: "product6",
title: "Product 6",
tags: ["clearance"],
variants: [{ id: "v6", price: "15.99" }],
},
{
id: "product7",
title: "Product 7",
tags: ["clearance"],
variants: [{ id: "v7", price: "12.99" }],
},
];
mockProductService.debugFetchAllProductTags.mockResolvedValue(
moreProducts
);
const result = await tagAnalysisService.getTagAnalysis();
expect(result.recommendations).toBeInstanceOf(Array);
expect(result.recommendations.length).toBeGreaterThan(0);
// Should have caution recommendation for 'sale' and 'clearance' tags
const cautionRec = result.recommendations.find(
(rec) => rec.type === "caution"
);
expect(cautionRec).toBeDefined();
expect(cautionRec.tags).toContain("sale");
expect(cautionRec.tags).toContain("clearance");
});
test("handles empty product list", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue([]);
await expect(tagAnalysisService.getTagAnalysis()).rejects.toThrow(
"No products found for tag analysis"
);
expect(mockProgressService.error).toHaveBeenCalledWith(
expect.stringContaining("Tag analysis failed")
);
});
test("handles products without tags", async () => {
const productsWithoutTags = [
{ id: "product1", title: "Product 1", tags: null, variants: [] },
{ id: "product2", title: "Product 2", tags: [], variants: [] },
{ id: "product3", title: "Product 3", variants: [] }, // no tags property
];
mockProductService.debugFetchAllProductTags.mockResolvedValue(
productsWithoutTags
);
const result = await tagAnalysisService.getTagAnalysis();
expect(result.totalProducts).toBe(3);
expect(result.tagCounts).toHaveLength(0);
expect(Object.keys(result.priceRanges)).toHaveLength(0);
});
test("caches results for performance", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
// First call
const result1 = await tagAnalysisService.getTagAnalysis(250);
// Second call should use cache
const result2 = await tagAnalysisService.getTagAnalysis(250);
expect(mockProductService.debugFetchAllProductTags).toHaveBeenCalledTimes(
1
);
expect(result1).toEqual(result2);
});
test("respects cache expiry", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
// Mock Date.now to control cache expiry
const originalDateNow = Date.now;
let mockTime = 1000000;
Date.now = jest.fn(() => mockTime);
// First call
await tagAnalysisService.getTagAnalysis(250);
// Advance time beyond cache expiry (5 minutes)
mockTime += 6 * 60 * 1000;
// Second call should fetch fresh data
await tagAnalysisService.getTagAnalysis(250);
expect(mockProductService.debugFetchAllProductTags).toHaveBeenCalledTimes(
2
);
// Restore original Date.now
Date.now = originalDateNow;
});
});
describe("Requirements Compliance", () => {
test("meets requirement 7.1 - analyzes available product tags and counts", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
const result = await tagAnalysisService.getTagAnalysis();
// Should provide tag counts
expect(result.tagCounts).toBeInstanceOf(Array);
expect(result.tagCounts.length).toBeGreaterThan(0);
result.tagCounts.forEach((tagInfo) => {
expect(tagInfo).toHaveProperty("tag");
expect(tagInfo).toHaveProperty("count");
expect(typeof tagInfo.count).toBe("number");
expect(tagInfo.count).toBeGreaterThan(0);
});
});
test("meets requirement 7.2 - shows sample products for selected tags", async () => {
const mockSampleProducts = [
{
id: "product1",
title: "Test Product 1",
tags: ["sale", "featured"],
variants: [
{
id: "variant1",
title: "Default",
price: "29.99",
compareAtPrice: "39.99",
},
],
},
];
mockProductService.fetchProductsByTag.mockResolvedValue(
mockSampleProducts
);
const samples = await tagAnalysisService.getSampleProductsForTag("sale");
// Should return sample products with essential info
expect(samples).toBeInstanceOf(Array);
samples.forEach((product) => {
expect(product).toHaveProperty("id");
expect(product).toHaveProperty("title");
expect(product).toHaveProperty("tags");
expect(product).toHaveProperty("variants");
});
});
test("meets requirement 7.3 - provides comprehensive tag analysis", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
const result = await tagAnalysisService.getTagAnalysis();
// Should provide comprehensive analysis
expect(result).toHaveProperty("totalProducts");
expect(result).toHaveProperty("tagCounts");
expect(result).toHaveProperty("priceRanges");
expect(result).toHaveProperty("recommendations");
expect(result).toHaveProperty("analyzedAt");
// Tag counts should be sorted and include percentages
expect(result.tagCounts[0].count).toBeGreaterThanOrEqual(
result.tagCounts[1]?.count || 0
);
result.tagCounts.forEach((tag) => {
expect(tag).toHaveProperty("percentage");
expect(typeof tag.percentage).toBe("number");
});
});
test("meets requirement 7.4 - provides tag recommendations", async () => {
mockProductService.debugFetchAllProductTags.mockResolvedValue(
mockProducts
);
const result = await tagAnalysisService.getTagAnalysis();
// Should provide recommendations
expect(result.recommendations).toBeInstanceOf(Array);
result.recommendations.forEach((rec) => {
expect(rec).toHaveProperty("type");
expect(rec).toHaveProperty("title");
expect(rec).toHaveProperty("description");
expect(rec).toHaveProperty("tags");
expect(rec).toHaveProperty("reason");
expect(rec).toHaveProperty("priority");
expect(rec).toHaveProperty("actionable");
expect(rec).toHaveProperty("estimatedImpact");
expect(Array.isArray(rec.tags)).toBe(true);
});
});
});
});