@developers-joyride/shortify
Version:
High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)
164 lines (130 loc) • 5.04 kB
text/typescript
import { ShortenerService } from "../src/services/shortener.service";
import { IDatabaseAdapter, IUrl } from "../src/interfaces/database.interface";
// Mock database adapter for testing collection name and uniqueness
class MockDatabaseAdapter implements IDatabaseAdapter {
private urls: Map<string, IUrl> = new Map();
private collectionName: string = "urls";
constructor(collectionName?: string) {
if (collectionName) {
this.collectionName = collectionName;
}
}
async connect(): Promise<boolean> {
return true;
}
async disconnect(): Promise<void> {}
isConnected(): boolean {
return true;
}
async createUrl(url: Omit<IUrl, "createdAt">): Promise<IUrl> {
const newUrl: IUrl = {
...url,
createdAt: new Date(),
};
this.urls.set(url.urlId, newUrl);
return newUrl;
}
async findUrlByUrlId(urlId: string): Promise<IUrl | null> {
return this.urls.get(urlId) || null;
}
async findUrlByOriginalUrl(originalUrl: string): Promise<IUrl | null> {
for (const url of this.urls.values()) {
if (url.originalUrl === originalUrl) {
return url;
}
}
return null;
}
async updateUrlClicks(urlId: string, clicks: number): Promise<void> {
const url = this.urls.get(urlId);
if (url) {
url.clicks = clicks;
}
}
async deleteUrl(urlId: string): Promise<boolean> {
return this.urls.delete(urlId);
}
on(event: string, listener: (...args: any[]) => void): this {
return this;
}
emit(event: string, ...args: any[]): boolean {
return true;
}
// Helper method to get collection name for testing
getCollectionName(): string {
return this.collectionName;
}
// Helper method to get all URLs for testing
getAllUrls(): IUrl[] {
return Array.from(this.urls.values());
}
}
describe("Collection Name and URL ID Uniqueness Tests", () => {
it("should preserve collection name case", () => {
const customCollectionName = "MyCustomCollection";
const mockDbAdapter = new MockDatabaseAdapter(customCollectionName);
expect(mockDbAdapter.getCollectionName()).toBe(customCollectionName);
});
it("should generate unique URL IDs for same original URL", async () => {
const mockDbAdapter = new MockDatabaseAdapter();
const service = new ShortenerService("https://example.com/", mockDbAdapter);
const originalUrl = "https://www.google.com";
// Create first shortened URL
const result1 = await service.shorten(originalUrl);
expect(result1.originalUrl).toBe(originalUrl);
expect(result1.urlId).toBeDefined();
// Create second shortened URL with same original URL
const result2 = await service.shorten(originalUrl);
expect(result2.originalUrl).toBe(originalUrl);
expect(result2.urlId).toBeDefined();
// URL IDs should be different
expect(result1.urlId).not.toBe(result2.urlId);
// Both should be stored in database
const allUrls = mockDbAdapter.getAllUrls();
expect(allUrls).toHaveLength(2);
expect(allUrls[0].urlId).not.toBe(allUrls[1].urlId);
});
it("should handle custom URL ID conflicts", async () => {
const mockDbAdapter = new MockDatabaseAdapter();
const service = new ShortenerService("https://example.com/", mockDbAdapter);
const originalUrl1 = "https://www.google.com";
const originalUrl2 = "https://www.github.com";
const customUrlId = "custom123";
// Create first URL with custom ID
const result1 = await service.shorten(originalUrl1, { customUrlId });
expect(result1.urlId).toBe(customUrlId);
// Try to create second URL with same custom ID
const result2 = await service.shorten(originalUrl2, { customUrlId });
// Should generate a new URL ID instead of using the same one
expect(result2.urlId).not.toBe(customUrlId);
expect(result2.urlId).toBeDefined();
// Both URLs should be stored
const allUrls = mockDbAdapter.getAllUrls();
expect(allUrls).toHaveLength(2);
expect(allUrls[0].urlId).not.toBe(allUrls[1].urlId);
});
it("should handle multiple URL ID conflicts gracefully", async () => {
const mockDbAdapter = new MockDatabaseAdapter();
const service = new ShortenerService("https://example.com/", mockDbAdapter);
// First, create a URL with a known ID to create a conflict
await service.shorten("https://www.example1.com", {
customUrlId: "conflict123",
});
// Mock the generateUrlId method to return predictable IDs for testing
const originalGenerateId = service["generateUrlId"];
let callCount = 0;
service["generateUrlId"] = () => {
callCount++;
// Return same ID for first few calls to simulate conflicts
if (callCount <= 3) {
return "conflict123";
}
return "unique456";
};
const result = await service.shorten("https://www.example.com");
// Should eventually get a unique ID
expect(result.urlId).toBe("unique456");
// Restore original method
service["generateUrlId"] = originalGenerateId;
});
});