@kenniy/godeye-data-contracts
Version:
Enterprise-grade base repository architecture for GOD-EYE microservices with zero overhead and maximum code reuse
307 lines (306 loc) • 13.5 kB
JavaScript
"use strict";
/**
* Backward Compatibility Tests
*
* Ensures all breaking changes are avoided and existing APIs remain functional
* Tests migration path from old response structure to new standardized format
*/
Object.defineProperty(exports, "__esModule", { value: true });
const response_1 = require("../core/response");
const mongoose_aggregate_repository_1 = require("../repositories/mongoose-aggregate.repository");
class TestBackwardCompatRepository extends mongoose_aggregate_repository_1.MongooseAggregateRepository {
constructor(model) {
super(model);
}
}
describe("Backward Compatibility Tests", () => {
let mockModel;
let repository;
beforeEach(() => {
const mockPaginatedResult = [
{
data: [
{ _id: "1", name: "Test 1", status: "ACTIVE" },
{ _id: "2", name: "Test 2", status: "ACTIVE" },
],
totalCount: [{ count: 2 }],
},
];
const mockExec = jest.fn().mockResolvedValue(mockPaginatedResult);
const mockAggregate = jest.fn().mockReturnValue({ exec: mockExec });
mockModel = {
aggregate: mockAggregate,
collection: { name: "test_entities" },
};
repository = new TestBackwardCompatRepository(mockModel);
});
describe("Response Structure Backward Compatibility", () => {
it("should auto-detect old pagination format and convert to new format", () => {
// Old format - separate pagination object (deprecated but still supported)
const oldFormatData = {
data: [{ id: "1", name: "Test" }],
pagination: {
total: 100,
page: 2,
limit: 20,
totalPages: 5,
hasNext: true,
hasPrev: true,
},
};
const response = response_1.ResponseFactory.success(oldFormatData);
// Should convert to new standardized format
expect(response.success).toBe(true);
expect(response.data.items).toEqual([{ id: "1", name: "Test" }]);
expect(response.data.total).toBe(100);
expect(response.data.page).toBe(2);
expect(response.data.limit).toBe(20);
expect(response.data.totalPages).toBe(5);
expect(response.data.hasNext).toBe(true);
expect(response.data.hasPrev).toBe(true);
});
it("should handle new items format correctly", () => {
// New standardized format
const newFormatData = {
items: [{ id: "1", name: "Test" }],
total: 50,
page: 1,
limit: 10,
};
const response = response_1.ResponseFactory.success(newFormatData);
expect(response.success).toBe(true);
expect(response.data.items).toEqual([{ id: "1", name: "Test" }]);
expect(response.data.total).toBe(50);
expect(response.data.totalPages).toBe(5);
});
it("should handle non-paginated data without modification", () => {
const simpleData = {
id: "1",
name: "Test User",
email: "test@example.com",
};
const response = response_1.ResponseFactory.success(simpleData);
expect(response.success).toBe(true);
expect(response.data).toEqual(simpleData);
// Should not add pagination properties to non-paginated data
expect(response.data.items).toBeUndefined();
expect(response.data.total).toBeUndefined();
});
it("should preserve metadata from repository results", () => {
const dataWithMetadata = {
items: [{ id: "1" }],
total: 1,
page: 1,
limit: 10,
metadata: {
queryTime: 45,
searchAlgorithms: ["btree_index"],
backendConditions: { status: "ACTIVE" },
},
};
const response = response_1.ResponseFactory.success(dataWithMetadata);
expect(response.metadata).toBeDefined();
expect(response.metadata?.queryTime).toBe(45);
expect(response.metadata?.searchAlgorithms).toEqual(["btree_index"]);
expect(response.metadata?.backendConditions).toEqual({
status: "ACTIVE",
});
});
});
describe("Repository Method Backward Compatibility", () => {
it("should maintain original complexQuery method behavior", async () => {
const config = {
conditions: { status: "ACTIVE" },
joins: [
{
collection: "users",
localField: "user_id",
foreignField: "_id",
as: "user",
},
],
aggregations: [
{
operation: "COUNT",
field: "_id",
alias: "total_count",
},
],
pagination: { page: 1, limit: 10 },
};
const result = await repository.complexQuery(config);
// Original interface should be preserved
expect(result).toHaveProperty("items");
expect(result).toHaveProperty("total");
expect(result).toHaveProperty("page");
expect(result).toHaveProperty("limit");
expect(result).toHaveProperty("totalPages");
expect(result).toHaveProperty("hasNext");
expect(result).toHaveProperty("hasPrev");
// Should NOT have enhanced features in original method
expect(result).not.toHaveProperty("metrics");
expect(result).not.toHaveProperty("suggestions");
expect(result).not.toHaveProperty("cacheInfo");
});
it("should maintain original aggregateWithPagination method behavior", async () => {
const pipeline = [
{ $match: { status: "ACTIVE" } },
{
$lookup: {
from: "users",
localField: "user_id",
foreignField: "_id",
as: "user",
},
},
];
const pagination = { page: 2, limit: 5 };
const result = await repository.aggregateWithPagination(pipeline, pagination);
expect(result).toHaveProperty("items");
expect(result).toHaveProperty("total");
expect(result.page).toBe(2);
expect(result.limit).toBe(5);
expect(typeof result.totalPages).toBe("number");
expect(typeof result.hasNext).toBe("boolean");
expect(typeof result.hasPrev).toBe("boolean");
});
it("should maintain original aggregate method behavior", async () => {
const pipeline = [
{ $match: { status: "ACTIVE" } },
{ $group: { _id: "$status", count: { $sum: 1 } } },
];
const result = await repository.aggregate(pipeline);
expect(Array.isArray(result)).toBe(true);
expect(mockModel.aggregate).toHaveBeenCalledWith(pipeline, expect.any(Object));
});
});
describe("Type Safety Backward Compatibility", () => {
it("should maintain PaginatedResult interface compatibility", () => {
const result = {
items: [{ _id: "1", name: "Test", status: "ACTIVE" }],
total: 1,
page: 1,
limit: 10,
totalPages: 1,
hasNext: false,
hasPrev: false,
};
// This should compile without errors
expect(result.items).toHaveLength(1);
expect(result.total).toBe(1);
});
it("should maintain ComplexQueryConfig interface compatibility", () => {
const config = {
conditions: { status: "ACTIVE" },
joins: [
{
collection: "users",
localField: "user_id",
foreignField: "_id",
as: "user",
},
],
aggregations: [
{
operation: "COUNT",
field: "_id",
alias: "count",
},
],
groupBy: ["status"],
sort: { created_at: -1 },
select: ["name", "status"],
pagination: { page: 1, limit: 10 },
};
// This should compile without errors and work with original methods
expect(config.conditions).toBeDefined();
expect(config.joins).toHaveLength(1);
expect(config.aggregations).toHaveLength(1);
});
});
describe("Error Handling Backward Compatibility", () => {
it("should maintain original error response format", () => {
const errorResponse = response_1.ResponseFactory.error("Validation failed", "Invalid user data", 400);
expect(errorResponse.success).toBe(false);
expect(errorResponse.error).toBe("Validation failed");
expect(errorResponse.message).toBe("Invalid user data");
expect(errorResponse.status_code).toBe(400);
expect(errorResponse.timestamp).toBeDefined();
expect(errorResponse.trace_id).toBeDefined();
});
it("should handle repository errors without breaking existing error handling", async () => {
const mockExecError = jest
.fn()
.mockRejectedValue(new Error("Database connection failed"));
mockModel.aggregate.mockImplementationOnce(() => ({
exec: mockExecError,
}));
const config = {
conditions: { status: "ACTIVE" },
};
await expect(repository.complexQuery(config)).rejects.toThrow("Database connection failed");
});
});
describe("Export Compatibility", () => {
it("should maintain all original exports", () => {
// Test that we can import everything that was available before
expect(response_1.ResponseFactory).toBeDefined();
expect(mongoose_aggregate_repository_1.MongooseAggregateRepository).toBeDefined();
expect(typeof response_1.ResponseFactory.success).toBe("function");
expect(typeof response_1.ResponseFactory.error).toBe("function");
expect(typeof response_1.ResponseFactory.paginated).toBe("function");
});
});
describe("Performance Backward Compatibility", () => {
it("should not introduce performance regression in original methods", async () => {
const startTime = Date.now();
const config = {
conditions: { status: "ACTIVE" },
pagination: { page: 1, limit: 10 },
};
await repository.complexQuery(config);
const executionTime = Date.now() - startTime;
// Should execute quickly (under 100ms for mocked calls)
expect(executionTime).toBeLessThan(100);
});
});
});
describe("Migration Path Tests", () => {
describe("Gradual Enhancement Adoption", () => {
it("should allow gradual migration from old to new aggregation methods", async () => {
const mockModel = {
aggregate: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue([
{
data: [{ _id: "1", name: "Test" }],
totalCount: [{ count: 1 }],
},
]),
}),
collection: { name: "test_entities" },
};
const repository = new TestBackwardCompatRepository(mockModel);
// Step 1: Use original method (still works)
const oldResult = await repository.complexQuery({
conditions: { status: "ACTIVE" },
pagination: { page: 1, limit: 10 },
});
expect(oldResult).toHaveProperty("items");
expect(oldResult).not.toHaveProperty("metrics");
// Step 2: Can upgrade to enhanced repository later without breaking changes
// (Enhanced repository extends the original, so all methods remain available)
});
});
describe("Version Compatibility", () => {
it("should work with existing package consumers", () => {
// Simulate how existing consumers use the package
const data = { items: [{ id: "1" }], total: 1, page: 1, limit: 10 };
const response = response_1.ResponseFactory.success(data);
// Existing consumers expect this structure
expect(response.success).toBe(true);
expect(response.data).toHaveProperty("items");
expect(response.status_code).toBe(200);
expect(response.timestamp).toBeDefined();
});
});
});