@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
1,047 lines (793 loc) • 38.3 kB
text/typescript
import "reflect-metadata";
import {
MetadataRegistry,
SchemaBuilder
} from "../../core";
import { PostgreSQLAdapter } from "../../adapters/postgresql";
import { User } from "../../examples/blog/User";
import { Post } from "../../examples/blog/Post";
import { Tag } from "../../examples/blog/Tag";
import { ComplexUser } from "../fixtures/ComplexUser";
/**
* PostgreSQL Adapter Bulletproof Tests
* These tests shoot AK-47s at the adapter to ensure it's bulletproof
*/
describe("PostgreSQLAdapter - Bulletproof Tests", () => {
let registry: MetadataRegistry;
let builder: SchemaBuilder;
let adapter: PostgreSQLAdapter;
let mockPool: any;
let mockClient: any;
let mockQueryResult: any;
beforeEach(() => {
registry = new MetadataRegistry();
builder = new SchemaBuilder(registry);
builder.registerEntities([User, Post, Tag, ComplexUser]);
// Debug: Check if ComplexUser was registered
const entityMetadata = registry.getEntityMetadata(ComplexUser);
if (!entityMetadata) {
console.log("WARNING: ComplexUser not registered in metadata registry");
}
// Create mock query result
mockQueryResult = {
rows: [],
rowCount: 0
};
// Create mock client
mockClient = {
query: jest.fn().mockResolvedValue(mockQueryResult),
release: jest.fn()
};
// Create mock pool
mockPool = {
connect: jest.fn().mockResolvedValue(mockClient),
end: jest.fn().mockResolvedValue(undefined)
};
adapter = new PostgreSQLAdapter(registry, "postgresql://localhost:5432/test");
// Mock the private pool property
(adapter as any).pool = mockPool;
(adapter as any).initialized = true;
});
afterEach(() => {
jest.clearAllMocks();
});
// ==============================================
// PHASE 1: FOUNDATION HARDENING - AK-47 ROUND 1
// ==============================================
describe("Phase 1: Foundation Hardening", () => {
describe("Connection Failure Scenarios", () => {
test("should handle invalid connection string gracefully", async () => {
const invalidAdapter = new PostgreSQLAdapter(registry, "invalid://connection");
// Mock the initialization to fail
(invalidAdapter as any).initialize = jest.fn().mockRejectedValue(new Error("invalid connection"));
// Force initialization to trigger connection
await expect(invalidAdapter.query(User, { id: "test" })).rejects.toThrow("invalid connection");
});
test("should handle connection timeout", async () => {
mockPool.connect.mockRejectedValueOnce(new Error("connection timeout"));
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("connection timeout");
});
test("should handle authentication failure", async () => {
mockPool.connect.mockRejectedValueOnce(new Error("authentication failed"));
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("authentication failed");
});
test("should handle connection pool exhaustion", async () => {
mockPool.connect.mockRejectedValueOnce(new Error("connection pool exhausted"));
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("connection pool exhausted");
});
test("should handle connection loss during operation", async () => {
mockClient.query.mockRejectedValueOnce(new Error("connection lost"));
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("connection lost");
// Verify client was released despite error
expect(mockClient.release).toHaveBeenCalled();
});
test("should handle null/undefined pool", async () => {
(adapter as any).pool = null;
(adapter as any).initialized = true;
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("Pool not initialized");
});
});
describe("SQL Injection Protection", () => {
test("should handle malicious SQL in criteria values", async () => {
const maliciousCriteria = {
id: "'; DROP TABLE users; --"
};
await adapter.query(User, maliciousCriteria);
// Verify parameterized query was used
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining(["'; DROP TABLE users; --"])
);
});
test("should handle SQL injection in multiple criteria", async () => {
const maliciousCriteria = {
id: "1; DELETE FROM users WHERE 1=1; --",
email: "test@example.com'; DROP TABLE posts; --"
};
await adapter.query(User, maliciousCriteria);
// Verify both values were parameterized
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining([
"1; DELETE FROM users WHERE 1=1; --",
"test@example.com'; DROP TABLE posts; --"
])
);
});
test("should handle SQL injection in native query parameters", async () => {
const maliciousParams = ["'; DROP TABLE users; --"];
await adapter.runNativeQuery("SELECT * FROM users WHERE id = $1", maliciousParams);
// Verify parameters were passed through (not escaped at this level)
expect(mockClient.query).toHaveBeenCalledWith(
"SELECT * FROM users WHERE id = $1",
maliciousParams
);
});
test("should handle null/undefined criteria safely", async () => {
await adapter.query(User, { id: null });
await adapter.query(User, { id: undefined });
// Should not throw and should handle null/undefined values
expect(mockClient.query).toHaveBeenCalledTimes(2);
});
test("should handle complex object criteria", async () => {
const complexCriteria = {
metadata: { key: "value; DROP TABLE users;" },
nested: {
deep: "'; DELETE FROM posts; --"
}
};
await adapter.query(User, complexCriteria);
// Should handle complex objects without injection
expect(mockClient.query).toHaveBeenCalled();
});
});
describe("Entity Validation Edge Cases", () => {
test("should handle circular reference in entities", async () => {
const user1 = new ComplexUser();
const user2 = new ComplexUser();
user1.id = "user1";
user1.name = "User 1";
user1.email = "user1@example.com";
user1.status = "active";
user1.managers = [user2];
user2.id = "user2";
user2.name = "User 2";
user2.email = "user2@example.com";
user2.status = "active";
user2.managers = [user1]; // Circular reference
// Should not cause infinite loop
await expect(adapter.save(user1)).resolves.not.toThrow();
});
test("should handle deeply nested relationship validation", async () => {
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
const post = new Post();
post.id = "post1";
post.title = "Test Post";
post.content = "Content";
post.author = user;
const tag = new Tag();
tag.id = "tag1";
tag.name = "Test Tag";
tag.posts = [post];
post.tags = [tag];
user.posts = [post];
// Should handle complex nested relationships
await expect(adapter.save(user)).resolves.not.toThrow();
});
test("should handle entity with missing required properties", async () => {
const invalidUser = new User();
invalidUser.id = "test";
// Missing required name and email
await expect(adapter.save(invalidUser)).rejects.toThrow(/Invalid entity/);
});
test("should handle entity with invalid property types", async () => {
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
// Invalid type assignment
(user as any).createdAt = "invalid-date";
// Should not throw during save (type coercion handled by database)
await expect(adapter.save(user)).resolves.not.toThrow();
});
test("should handle entity with undefined relationships", async () => {
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
user.posts = undefined as any;
await expect(adapter.save(user)).resolves.not.toThrow();
});
test("should handle entity with null ID during save", async () => {
const user = new User();
user.id = null as any;
user.name = "Test User";
user.email = "test@example.com";
// Should handle null ID (insert operation)
await expect(adapter.save(user)).resolves.not.toThrow();
});
test("should handle entity with duplicate unique properties", async () => {
mockClient.query.mockRejectedValueOnce(new Error("duplicate key value violates unique constraint"));
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "existing@example.com";
await expect(adapter.save(user)).rejects.toThrow("duplicate key value violates unique constraint");
});
});
describe("Query Parameter Edge Cases", () => {
test("should handle empty criteria object", async () => {
await adapter.query(User, {});
// Should generate query without WHERE clause
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringMatching(/SELECT \* FROM \w+\s*LIMIT 1/),
[]
);
});
test("should handle criteria with special characters", async () => {
const criteria = {
name: "Test User @#$%^&*()_+-=[]{}|;':,./<>?"
};
await adapter.query(User, criteria);
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining(["Test User @#$%^&*()_+-=[]{}|;':,./<>?"])
);
});
test("should handle criteria with unicode characters", async () => {
const criteria = {
name: "用户测试 🚀 émojis"
};
await adapter.query(User, criteria);
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining(["用户测试 🚀 émojis"])
);
});
test("should handle very long string criteria", async () => {
const longString = "a".repeat(10000);
const criteria = {
name: longString
};
await adapter.query(User, criteria);
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining([longString])
);
});
test("should handle array values in criteria", async () => {
const criteria = {
id: ["1", "2", "3"]
};
await adapter.query(User, criteria);
// Should handle array values appropriately
expect(mockClient.query).toHaveBeenCalled();
});
test("should handle boolean values in criteria", async () => {
const criteria = {
active: true
};
await adapter.query(User, criteria);
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$1"),
expect.arrayContaining([true])
);
});
test("should handle numeric values in criteria", async () => {
const criteria = {
age: 25,
score: 99.5,
bigNumber: 9007199254740991
};
await adapter.query(User, criteria);
expect(mockClient.query).toHaveBeenCalledWith(
expect.stringContaining("$3"),
expect.arrayContaining([25, 99.5, 9007199254740991])
);
});
});
describe("Database Operation Edge Cases", () => {
test("should handle query with no results", async () => {
mockQueryResult.rows = [];
mockQueryResult.rowCount = 0;
const result = await adapter.query(User, { id: "nonexistent" });
expect(result).toBeNull();
});
test("should handle queryMany with no results", async () => {
mockQueryResult.rows = [];
mockQueryResult.rowCount = 0;
const results = await adapter.queryMany(User, { status: "active" });
expect(results).toEqual([]);
});
test("should handle delete with no matching records", async () => {
mockQueryResult.rowCount = 0;
await expect(adapter.delete(User, "nonexistent")).resolves.not.toThrow();
});
test("should handle runNativeQuery with syntax error", async () => {
mockClient.query.mockRejectedValueOnce(new Error("syntax error at or near"));
await expect(adapter.runNativeQuery("SELEC * FROM users")).rejects.toThrow("syntax error at or near");
});
test("should handle runNativeQuery with permission error", async () => {
mockClient.query.mockRejectedValueOnce(new Error("permission denied"));
await expect(adapter.runNativeQuery("DROP TABLE users")).rejects.toThrow("permission denied");
});
test("should handle client release failure", async () => {
mockClient.release.mockImplementation(() => {
throw new Error("release failed");
});
// Should throw because release failure bubbles up
await expect(adapter.query(User, { id: "test" })).rejects.toThrow("release failed");
});
test("should handle adapter close with no pool", async () => {
(adapter as any).pool = null;
await expect(adapter.close()).resolves.not.toThrow();
});
test("should handle adapter close with pool end failure", async () => {
mockPool.end.mockRejectedValueOnce(new Error("pool end failed"));
await expect(adapter.close()).rejects.toThrow("pool end failed");
});
});
});
// ==============================================
// PHASE 2: TRANSACTION WARFARE - AK-47 ROUND 2
// ==============================================
describe("Phase 2: Transaction Warfare", () => {
describe("Transaction Rollback Scenarios", () => {
test("should handle transaction rollback on save failure", async () => {
// Mock successful BEGIN, then failure on INSERT, then successful ROLLBACK
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check query
.mockRejectedValueOnce(new Error("constraint violation")) // INSERT fails
.mockResolvedValueOnce({ rows: [], rowCount: 0 }); // ROLLBACK
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
await expect(adapter.save(user)).rejects.toThrow("constraint violation");
// Verify BEGIN, ID check, failed INSERT, and ROLLBACK were called
expect(mockClient.query).toHaveBeenCalledWith("BEGIN");
expect(mockClient.query).toHaveBeenCalledWith("ROLLBACK");
});
test("should handle rollback failure after save failure", async () => {
// Mock successful BEGIN, then failure on INSERT, then failure on ROLLBACK
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check query
.mockRejectedValueOnce(new Error("constraint violation")) // INSERT fails
.mockRejectedValueOnce(new Error("rollback failed")); // ROLLBACK fails
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
// Should throw the rollback error, not the original error
await expect(adapter.save(user)).rejects.toThrow("rollback failed");
});
test("should handle nested transaction failures", async () => {
// Mock a scenario where relationship saves trigger nested transactions
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
const post = new Post();
post.id = "post1";
post.title = "Test Post";
post.content = "Content";
post.author = user;
user.posts = [post];
// Mock the main transaction succeeding but relationship transaction failing
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check query
.mockResolvedValueOnce({ rows: [{ id: "test" }], rowCount: 1 }) // INSERT user
.mockRejectedValueOnce(new Error("relationship constraint violation")) // Related entity save fails
.mockResolvedValueOnce({ rows: [], rowCount: 0 }); // ROLLBACK
await expect(adapter.save(user)).rejects.toThrow("relationship constraint violation");
});
test("should handle connection loss during transaction", async () => {
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockRejectedValueOnce(new Error("connection lost")); // Connection fails during transaction
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
await expect(adapter.save(user)).rejects.toThrow("connection lost");
});
test("should handle deadlock detection and retry", async () => {
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check query
.mockRejectedValueOnce(new Error("deadlock detected")); // Deadlock on INSERT
const user = new User();
user.id = "test";
user.name = "Test User";
user.email = "test@example.com";
await expect(adapter.save(user)).rejects.toThrow("deadlock detected");
});
});
describe("Concurrent Operation Testing", () => {
test("should handle multiple saves of same entity", async () => {
const user = new User();
user.id = "concurrent-test";
user.name = "Concurrent User";
user.email = "concurrent@example.com";
// Mock different responses for each concurrent save
let callCount = 0;
mockClient.query.mockImplementation(() => {
callCount++;
if (callCount <= 3) {
return Promise.resolve({ rows: [], rowCount: 0 }); // First set of calls
} else if (callCount === 4) {
return Promise.reject(new Error("concurrent modification")); // Conflict
} else {
return Promise.resolve({ rows: [], rowCount: 0 }); // Remaining calls
}
});
// Simulate concurrent saves
const savePromises = [
adapter.save(user),
adapter.save(user),
adapter.save(user)
];
// At least one should fail due to concurrent modification
const results = await Promise.allSettled(savePromises);
const hasFailure = results.some(result => result.status === "rejected");
expect(hasFailure).toBe(true);
});
test("should handle race conditions in relationships", async () => {
const user = new User();
user.id = "race-test";
user.name = "Race User";
user.email = "race@example.com";
const post1 = new Post();
post1.id = "post1";
post1.title = "Post 1";
post1.content = "Content 1";
post1.author = user;
const post2 = new Post();
post2.id = "post2";
post2.title = "Post 2";
post2.content = "Content 2";
post2.author = user;
user.posts = [post1, post2];
// Mock race condition in relationship handling
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "race-test" }], rowCount: 1 }) // INSERT user
.mockRejectedValueOnce(new Error("foreign key constraint violation")); // Race in FK
await expect(adapter.save(user)).rejects.toThrow("foreign key constraint violation");
});
test("should handle connection pool contention", async () => {
// Simulate pool exhaustion
mockPool.connect
.mockRejectedValueOnce(new Error("pool exhausted"))
.mockRejectedValueOnce(new Error("pool exhausted"))
.mockResolvedValueOnce(mockClient); // Finally succeeds
const user = new User();
user.id = "pool-test";
user.name = "Pool User";
user.email = "pool@example.com";
// First two attempts should fail
await expect(adapter.query(User, { id: "test1" })).rejects.toThrow("pool exhausted");
await expect(adapter.query(User, { id: "test2" })).rejects.toThrow("pool exhausted");
// Third should succeed
await expect(adapter.query(User, { id: "test3" })).resolves.toBeNull();
});
test("should handle atomic operation failures", async () => {
// Test atomic operations that must either all succeed or all fail
const user = new User();
user.id = "atomic-test";
user.name = "Atomic User";
user.email = "atomic@example.com";
// Mock partial success followed by failure
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "atomic-test" }], rowCount: 1 }) // User insert succeeds
.mockRejectedValueOnce(new Error("index violation")) // Related operation fails
.mockResolvedValueOnce({ rows: [], rowCount: 0 }); // ROLLBACK
await expect(adapter.save(user)).rejects.toThrow("index violation");
// Verify rollback was called
expect(mockClient.query).toHaveBeenCalledWith("ROLLBACK");
});
});
});
// ==============================================
// PHASE 3: RELATIONSHIP COMPLEXITY - AK-47 ROUND 3
// ==============================================
describe("Phase 3: Relationship Complexity", () => {
describe("Complex Relationship Scenarios", () => {
test("should handle self-referencing entity relationships", async () => {
const manager = new ComplexUser();
manager.id = "manager1";
manager.name = "Manager";
manager.email = "manager@example.com";
manager.status = "active";
const employee = new ComplexUser();
employee.id = "employee1";
employee.name = "Employee";
employee.email = "employee@example.com";
employee.status = "active";
employee.managers = [manager];
manager.managedUsers = [employee];
// Should handle self-referencing relationships
await expect(adapter.save(manager)).resolves.not.toThrow();
});
test("should handle orphaned relationship cleanup", async () => {
const user = new User();
user.id = "orphan-test";
user.name = "Orphan User";
user.email = "orphan@example.com";
// Mock scenario where related entities become orphaned
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "orphan-test" }], rowCount: 1 }) // User insert
.mockRejectedValueOnce(new Error("orphaned foreign key")) // Orphan cleanup fails
.mockResolvedValueOnce({ rows: [], rowCount: 0 }); // ROLLBACK
await expect(adapter.save(user)).rejects.toThrow("orphaned foreign key");
});
test("should handle deeply nested relationship saves", async () => {
const user = new User();
user.id = "deep-test";
user.name = "Deep User";
user.email = "deep@example.com";
const post = new Post();
post.id = "deep-post";
post.title = "Deep Post";
post.content = "Content";
post.author = user;
const tag1 = new Tag();
tag1.id = "tag1";
tag1.name = "Tag 1";
tag1.posts = [post];
const tag2 = new Tag();
tag2.id = "tag2";
tag2.name = "Tag 2";
tag2.posts = [post];
post.tags = [tag1, tag2];
user.posts = [post];
// Mock successful deep save
mockClient.query.mockResolvedValue({ rows: [{ id: "test" }], rowCount: 1 });
await expect(adapter.save(user)).resolves.not.toThrow();
});
});
describe("Join Table Operations", () => {
test("should handle many-to-many join table failures", async () => {
const user = new User();
user.id = "join-test";
user.name = "Join User";
user.email = "join@example.com";
const follower = new User();
follower.id = "follower1";
follower.name = "Follower";
follower.email = "follower@example.com";
user.followers = [follower];
// Mock join table operation failure
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "join-test" }], rowCount: 1 }) // User insert
.mockRejectedValueOnce(new Error("join table constraint violation")); // Join table fails
await expect(adapter.save(user)).rejects.toThrow("join table constraint violation");
});
test("should handle join table constraint violations", async () => {
// Test duplicate relationship entries
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "test" }], rowCount: 1 }) // User insert
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // Delete existing relationships
.mockRejectedValueOnce(new Error("duplicate key value violates unique constraint")); // Duplicate join
const user = new User();
user.id = "duplicate-test";
user.name = "Duplicate User";
user.email = "duplicate@example.com";
const follower = new User();
follower.id = "follower1";
follower.name = "Follower";
follower.email = "follower@example.com";
user.followers = [follower, follower]; // Duplicate relationship
await expect(adapter.save(user)).rejects.toThrow("duplicate key value violates unique constraint");
});
test("should handle foreign key constraint violations", async () => {
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "test" }], rowCount: 1 }) // User insert
.mockRejectedValueOnce(new Error("foreign key constraint violation")); // FK violation
const user = new User();
user.id = "fk-test";
user.name = "FK User";
user.email = "fk@example.com";
const post = new Post();
post.id = "invalid-post";
post.title = "Invalid Post";
post.content = "Content";
post.author = user;
user.posts = [post];
await expect(adapter.save(user)).rejects.toThrow("foreign key constraint violation");
});
});
});
// ==============================================
// PHASE 4: DATA INTEGRITY ARTILLERY - AK-47 ROUND 4
// ==============================================
describe("Phase 4: Data Integrity Artillery", () => {
describe("Schema Evolution Scenarios", () => {
test("should handle missing table gracefully", async () => {
mockClient.query.mockRejectedValueOnce(new Error('relation "missing_table" does not exist'));
await expect(adapter.query(User, { id: "test" })).rejects.toThrow('relation "missing_table" does not exist');
});
test("should handle missing column gracefully", async () => {
mockClient.query.mockRejectedValueOnce(new Error('column "missing_column" does not exist'));
await expect(adapter.query(User, { missingColumn: "value" })).rejects.toThrow('column "missing_column" does not exist');
});
test("should handle column type mismatch", async () => {
mockClient.query.mockRejectedValueOnce(new Error("column type mismatch"));
const user = new User();
user.id = "type-test";
user.name = "Type User";
user.email = "type@example.com";
await expect(adapter.save(user)).rejects.toThrow("column type mismatch");
});
test("should handle index creation failures", async () => {
mockClient.query.mockRejectedValueOnce(new Error("index creation failed"));
await expect(adapter.runNativeQuery("CREATE INDEX test_idx ON users(name)")).rejects.toThrow("index creation failed");
});
});
describe("Data Corruption Scenarios", () => {
test("should handle partial write failures", async () => {
// Mock scenario where some data is written but transaction fails
mockClient.query
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // BEGIN
.mockResolvedValueOnce({ rows: [], rowCount: 0 }) // ID check
.mockResolvedValueOnce({ rows: [{ id: "partial" }], rowCount: 1 }) // Partial write succeeds
.mockRejectedValueOnce(new Error("disk full")) // Next operation fails
.mockResolvedValueOnce({ rows: [], rowCount: 0 }); // ROLLBACK
const user = new User();
user.id = "partial-test";
user.name = "Partial User";
user.email = "partial@example.com";
await expect(adapter.save(user)).rejects.toThrow("disk full");
expect(mockClient.query).toHaveBeenCalledWith("ROLLBACK");
});
test("should handle encoding issues", async () => {
const user = new User();
user.id = "encoding-test";
user.name = "Encoding \x00\x01\x02 User"; // Binary data
user.email = "encoding@example.com";
mockClient.query.mockRejectedValueOnce(new Error("invalid byte sequence"));
await expect(adapter.save(user)).rejects.toThrow("invalid byte sequence");
});
test("should handle very large data", async () => {
const largeContent = "x".repeat(1000000); // 1MB string
const user = new User();
user.id = "large-test";
user.name = largeContent;
user.email = "large@example.com";
mockClient.query.mockRejectedValueOnce(new Error("value too long"));
await expect(adapter.save(user)).rejects.toThrow("value too long");
});
test("should handle binary data edge cases", async () => {
const binaryData = Buffer.from([0x00, 0x01, 0xFF, 0xFE]);
const criteria = {
data: binaryData
};
await adapter.query(User, criteria);
// Should handle binary data in parameters
expect(mockClient.query).toHaveBeenCalledWith(
expect.any(String),
expect.arrayContaining([binaryData])
);
});
});
});
// ==============================================
// PHASE 5: PERFORMANCE UNDER FIRE - AK-47 ROUND 5
// ==============================================
describe("Phase 5: Performance Under Fire", () => {
describe("Resource Exhaustion Testing", () => {
test("should handle memory leaks in connection pool", async () => {
// Simulate memory pressure
const originalMemoryUsage = process.memoryUsage();
// Create many connections without proper cleanup
for (let i = 0; i < 100; i++) {
mockPool.connect.mockResolvedValueOnce({
query: jest.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
release: jest.fn() // Don't actually release
});
await adapter.query(User, { id: `test${i}` });
}
const currentMemoryUsage = process.memoryUsage();
// Memory usage should not grow significantly
const memoryGrowth = currentMemoryUsage.heapUsed - originalMemoryUsage.heapUsed;
expect(memoryGrowth).toBeLessThan(50 * 1024 * 1024); // Less than 50MB growth
});
test("should handle large result set processing", async () => {
// Mock a very large result set
const largeResultSet = Array.from({ length: 10000 }, (_, i) => ({
id: `user${i}`,
name: `User ${i}`,
email: `user${i}.com`
}));
mockQueryResult.rows = largeResultSet;
mockQueryResult.rowCount = largeResultSet.length;
const results = await adapter.queryMany(User, { active: true });
expect(results).toHaveLength(10000);
expect(results[0]).toBeInstanceOf(User);
});
test("should handle query timeout scenarios", async () => {
mockClient.query.mockImplementation(() => {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("query timeout")), 100);
});
});
await expect(adapter.query(User, { id: "timeout-test" })).rejects.toThrow("query timeout");
});
test("should handle connection cleanup verification", async () => {
const releaseSpy = jest.spyOn(mockClient, 'release');
// Perform multiple operations
await adapter.query(User, { id: "cleanup1" });
await adapter.query(User, { id: "cleanup2" });
await adapter.query(User, { id: "cleanup3" });
// Verify all connections were properly released
expect(releaseSpy).toHaveBeenCalledTimes(3);
});
});
describe("Optimization Edge Cases", () => {
test("should handle query plan failures", async () => {
mockClient.query.mockRejectedValueOnce(new Error("could not devise a query plan"));
await expect(adapter.query(User, { complexCriteria: "value" })).rejects.toThrow("could not devise a query plan");
});
test("should handle index usage verification", async () => {
// Mock query execution with index information
mockClient.query.mockResolvedValueOnce({
rows: [{
query_plan: "Index Scan using user_email_idx on users",
execution_time: 1.5
}],
rowCount: 1
});
const result = await adapter.runNativeQuery("EXPLAIN ANALYZE SELECT * FROM users WHERE email = $1", ["test@example.com"]) as any;
expect(result.rows[0].query_plan).toContain("Index Scan");
});
test("should handle bulk operation limits", async () => {
// Create a large number of entities to save
const users = Array.from({ length: 1000 }, (_, i) => {
const user = new User();
user.id = `bulk${i}`;
user.name = `Bulk User ${i}`;
user.email = `bulk${i}.com`;
return user;
});
// Mock bulk operation that hits limits
mockClient.query.mockRejectedValueOnce(new Error("too many parameters"));
// Attempt to save all users in one transaction (should fail)
for (const user of users) {
await expect(adapter.save(user)).rejects.toThrow("too many parameters");
break; // Only test first one
}
});
test("should handle connection reuse patterns", async () => {
const connectSpy = jest.spyOn(mockPool, 'connect');
// Perform multiple rapid operations
const operations = [
adapter.query(User, { id: "reuse1" }),
adapter.query(User, { id: "reuse2" }),
adapter.query(User, { id: "reuse3" }),
adapter.query(User, { id: "reuse4" }),
adapter.query(User, { id: "reuse5" })
];
await Promise.all(operations);
// Verify connections were requested (reuse is handled by pool)
expect(connectSpy).toHaveBeenCalledTimes(5);
});
});
});
});