mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
feat(api-nest): add test data factories and setup for E2E testing
- Introduced various factory classes (UserFactory, LibraryItemFactory, HighlightFactory, LabelFactory) to streamline test data generation using Faker.js. - Implemented global setup and teardown scripts for initializing and cleaning up PostgreSQL containers during tests. - Created a base factory class to provide common methods for building and creating entities in memory or in the database. - Updated Jest configuration to include global setup and teardown, enhancing test isolation and environment management.
This commit is contained in:
parent
b0e508fe4e
commit
73cf90b4ea
16 changed files with 1400 additions and 7 deletions
69
packages/api-nest/.env.test.example
Normal file
69
packages/api-nest/.env.test.example
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Test Environment Configuration
|
||||
# Copy this file to .env.test and update values as needed
|
||||
|
||||
# ==========================================
|
||||
# DATABASE CONFIGURATION (Test Database)
|
||||
# ==========================================
|
||||
# CRITICAL: Use separate test database to prevent polluting development data
|
||||
TEST_DATABASE_NAME=omnivore_test
|
||||
TEST_DATABASE_HOST=localhost
|
||||
TEST_DATABASE_PORT=5432
|
||||
TEST_DATABASE_USER=app_user
|
||||
TEST_DATABASE_PASSWORD=
|
||||
|
||||
# ==========================================
|
||||
# REDIS CONFIGURATION (Test Redis)
|
||||
# ==========================================
|
||||
# Recommended: Use separate Redis database number for tests (0-15)
|
||||
REDIS_URL=redis://localhost:6379/1
|
||||
# Or use separate Redis instance:
|
||||
# TEST_REDIS_HOST=localhost
|
||||
# TEST_REDIS_PORT=6380
|
||||
|
||||
# ==========================================
|
||||
# AUTHENTICATION & SECURITY
|
||||
# ==========================================
|
||||
# Use test credentials (NOT production values)
|
||||
JWT_SECRET=test-jwt-secret-change-me
|
||||
JWT_EXPIRATION=24h
|
||||
|
||||
# Google OAuth (Test Credentials)
|
||||
GOOGLE_CLIENT_ID=test-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=test-google-client-secret
|
||||
GOOGLE_CALLBACK_URL=http://localhost:4001/api/v2/auth/google/callback
|
||||
|
||||
# ==========================================
|
||||
# APPLICATION SETTINGS
|
||||
# ==========================================
|
||||
NODE_ENV=test
|
||||
PORT=4001
|
||||
API_BASE_URL=http://localhost:4001
|
||||
|
||||
# ==========================================
|
||||
# LOGGING
|
||||
# ==========================================
|
||||
# Reduce log noise during tests
|
||||
LOG_LEVEL=error
|
||||
ENABLE_QUERY_LOGGING=false
|
||||
|
||||
# ==========================================
|
||||
# QUEUE & BACKGROUND JOBS
|
||||
# ==========================================
|
||||
# Use lower concurrency for tests
|
||||
QUEUE_WORKER_CONCURRENCY=1
|
||||
QUEUE_JOB_ATTEMPTS=1
|
||||
|
||||
# ==========================================
|
||||
# CONTENT PROCESSING (Test Settings)
|
||||
# ==========================================
|
||||
# Use faster/simpler settings for tests
|
||||
CONTENT_FETCH_TIMEOUT=5000
|
||||
MAX_CONTENT_SIZE=1048576
|
||||
|
||||
# ==========================================
|
||||
# NOTES FOR DEVELOPERS
|
||||
# ==========================================
|
||||
# 1. Never commit .env.test with real credentials
|
||||
# 2. Run migrations before tests: TEST_DATABASE_NAME=omnivore_test npm run migration:run
|
||||
# 3. Create test database: psql -U postgres -c "CREATE DATABASE omnivore_test;"
|
||||
# 4. See TESTING.md for complete setup instructions
|
||||
|
|
@ -64,9 +64,11 @@
|
|||
"typeorm": "^0.3.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@testcontainers/postgresql": "^11.7.1",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.14",
|
||||
|
|
|
|||
99
packages/api-nest/test/factories-example.e2e-spec.ts
Normal file
99
packages/api-nest/test/factories-example.e2e-spec.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Factory Pattern Example E2E Test
|
||||
*
|
||||
* This test demonstrates using Testcontainers + Factories
|
||||
* for easy, isolated test data generation.
|
||||
*
|
||||
* Run with: yarn test:e2e --testPathPattern=factories-example
|
||||
*/
|
||||
|
||||
import { UserFactory, LibraryItemFactory, HighlightFactory, LabelFactory } from './factories'
|
||||
import { getTestDataSource } from './setup/test-datasource'
|
||||
import { UserRole } from '../src/user/enums/user-role.enum'
|
||||
import { StatusType } from '../src/user/entities/user.entity'
|
||||
|
||||
describe('Factory Pattern Example (e2e)', () => {
|
||||
it('should create test data using factories', async () => {
|
||||
// Verify testcontainer datasource is available
|
||||
const dataSource = getTestDataSource()
|
||||
expect(dataSource.isInitialized).toBe(true)
|
||||
|
||||
// Create a user
|
||||
const user = await UserFactory.create({
|
||||
email: 'factory-test@example.com',
|
||||
name: 'Factory Test User',
|
||||
})
|
||||
|
||||
expect(user.id).toBeDefined()
|
||||
expect(user.email).toBe('factory-test@example.com')
|
||||
expect(user.password).toBeDefined() // bcrypt hash
|
||||
|
||||
// Create a library item for this user
|
||||
const item = await LibraryItemFactory.create({
|
||||
userId: user.id,
|
||||
title: 'Amazing Article About Testing',
|
||||
})
|
||||
|
||||
expect(item.id).toBeDefined()
|
||||
expect(item.userId).toBe(user.id)
|
||||
expect(item.title).toBe('Amazing Article About Testing')
|
||||
expect(item.slug).toBeDefined() // Slug is auto-generated by factory
|
||||
|
||||
// Create highlights for the article
|
||||
const highlight1 = await HighlightFactory.withColor(item.id, user.id, 'yellow')
|
||||
const highlight2 = await HighlightFactory.withColor(item.id, user.id, 'red')
|
||||
|
||||
expect(highlight1.color).toBe('yellow')
|
||||
expect(highlight2.color).toBe('red')
|
||||
expect(highlight1.quote).toBeDefined() // Faker-generated
|
||||
|
||||
// Create labels
|
||||
const labels = await LabelFactory.createManyForUser(user.id, 3)
|
||||
|
||||
expect(labels).toHaveLength(3)
|
||||
expect(labels[0].name).toBeDefined()
|
||||
expect(labels[0].color).toMatch(/^#[0-9A-F]{6}$/i) // Hex color
|
||||
|
||||
console.log('✅ Successfully created test data with factories!')
|
||||
console.log(' - User:', user.email)
|
||||
console.log(' - Library Item:', item.title)
|
||||
console.log(' - Highlights:', 2)
|
||||
console.log(' - Labels:', labels.length)
|
||||
})
|
||||
|
||||
it('should build entities in memory (for unit tests)', () => {
|
||||
// Build without saving to database (for mocking)
|
||||
const user = UserFactory.build({
|
||||
email: 'mock@example.com',
|
||||
role: UserRole.ADMIN,
|
||||
})
|
||||
|
||||
expect(user.email).toBe('mock@example.com')
|
||||
expect(user.role).toBe(UserRole.ADMIN)
|
||||
// No database call was made
|
||||
|
||||
const item = LibraryItemFactory.buildArchived(user.id)
|
||||
expect(item.folder).toBe('archive')
|
||||
expect(item.state).toBe('ARCHIVED')
|
||||
// No database call was made
|
||||
})
|
||||
|
||||
it('should use factory helper methods', async () => {
|
||||
const user = await UserFactory.create()
|
||||
|
||||
// Helper methods for common scenarios
|
||||
const adminUser = await UserFactory.admin()
|
||||
expect(adminUser.role).toBe(UserRole.ADMIN)
|
||||
|
||||
const pendingUser = await UserFactory.pending()
|
||||
expect(pendingUser.status).toBe(StatusType.PENDING)
|
||||
|
||||
const archivedItem = await LibraryItemFactory.archived(user.id)
|
||||
expect(archivedItem.folder).toBe('archive')
|
||||
|
||||
const itemWithProgress = await LibraryItemFactory.withProgress(user.id, 75)
|
||||
expect(itemWithProgress.readingProgressTopPercent).toBe(75)
|
||||
|
||||
console.log('✅ Factory helper methods work perfectly!')
|
||||
})
|
||||
})
|
||||
117
packages/api-nest/test/factories/base.factory.ts
Normal file
117
packages/api-nest/test/factories/base.factory.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { DeepPartial, Repository } from 'typeorm'
|
||||
import { getTestDataSource } from '../setup/test-datasource'
|
||||
|
||||
/**
|
||||
* Base Factory for generating test data
|
||||
* Provides two methods:
|
||||
* - build(): Creates entity in memory (for unit tests with mocks)
|
||||
* - create(): Saves entity to database (for integration/E2E tests)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Unit test (no database)
|
||||
* const user = UserFactory.build({ email: 'test@example.com' })
|
||||
*
|
||||
* // Integration test (with database)
|
||||
* const user = await UserFactory.create({ email: 'test@example.com' })
|
||||
* ```
|
||||
*/
|
||||
export abstract class BaseFactory<Entity> {
|
||||
/**
|
||||
* Build an entity in memory without saving to database
|
||||
* Useful for unit tests where you mock repositories
|
||||
*
|
||||
* @param overrides - Partial entity properties to override defaults
|
||||
* @returns Entity instance (not saved to DB)
|
||||
*/
|
||||
build(overrides?: DeepPartial<Entity>): Entity {
|
||||
const defaults = this.generateDefaults()
|
||||
return {
|
||||
...defaults,
|
||||
...(overrides || {}),
|
||||
} as Entity
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save an entity to the test database
|
||||
* Useful for integration/E2E tests that need real database data
|
||||
*
|
||||
* @param overrides - Partial entity properties to override defaults
|
||||
* @returns Promise of saved entity instance
|
||||
*/
|
||||
async create(overrides?: DeepPartial<Entity>): Promise<Entity> {
|
||||
const entity = this.build(overrides)
|
||||
const repository = this.getRepository()
|
||||
return await repository.save(entity as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple entities in the database
|
||||
* Efficient for seeding test data
|
||||
*
|
||||
* @param count - Number of entities to create
|
||||
* @param overrides - Properties to apply to all entities
|
||||
* @returns Promise of array of saved entities
|
||||
*/
|
||||
async createMany(
|
||||
count: number,
|
||||
overrides?: DeepPartial<Entity>,
|
||||
): Promise<Entity[]> {
|
||||
const entities: Entity[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities.push(await this.create(overrides))
|
||||
}
|
||||
|
||||
return entities
|
||||
}
|
||||
|
||||
/**
|
||||
* Build multiple entities in memory
|
||||
*
|
||||
* @param count - Number of entities to build
|
||||
* @param overrides - Properties to apply to all entities
|
||||
* @returns Array of entity instances (not saved)
|
||||
*/
|
||||
buildMany(count: number, overrides?: DeepPartial<Entity>): Entity[] {
|
||||
const entities: Entity[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
entities.push(this.build(overrides))
|
||||
}
|
||||
|
||||
return entities
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate default properties for the entity
|
||||
* Must be implemented by each factory to provide entity-specific defaults
|
||||
*
|
||||
* @returns Partial entity with default properties
|
||||
* @protected
|
||||
*/
|
||||
protected abstract generateDefaults(): DeepPartial<Entity>
|
||||
|
||||
/**
|
||||
* Get the TypeORM repository for this entity
|
||||
* Used by create() to save to database
|
||||
*
|
||||
* @returns TypeORM Repository instance
|
||||
* @protected
|
||||
*/
|
||||
protected abstract getRepository(): Repository<Entity>
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a repository from the test DataSource
|
||||
* Used by factory implementations
|
||||
*
|
||||
* @param entityClass - Entity class to get repository for
|
||||
* @returns TypeORM Repository instance
|
||||
*/
|
||||
export function getTestRepository<Entity>(
|
||||
entityClass: new () => Entity,
|
||||
): Repository<Entity> {
|
||||
const dataSource = getTestDataSource()
|
||||
return dataSource.getRepository(entityClass)
|
||||
}
|
||||
144
packages/api-nest/test/factories/highlight.factory.ts
Normal file
144
packages/api-nest/test/factories/highlight.factory.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { faker } from '@faker-js/faker'
|
||||
import { Repository } from 'typeorm'
|
||||
import {
|
||||
HighlightEntity,
|
||||
HighlightType,
|
||||
} from '../../src/highlight/entities/highlight.entity'
|
||||
import { BaseFactory, getTestRepository } from './base.factory'
|
||||
|
||||
/**
|
||||
* HighlightFactory - Generate test highlights
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a highlight
|
||||
* const highlight = await HighlightFactory.create({
|
||||
* libraryItemId: item.id,
|
||||
* userId: user.id
|
||||
* })
|
||||
*
|
||||
* // Use helper methods
|
||||
* const redHighlight = await HighlightFactory.withColor(item.id, user.id, 'red')
|
||||
* ```
|
||||
*/
|
||||
class HighlightFactoryClass extends BaseFactory<HighlightEntity> {
|
||||
protected generateDefaults() {
|
||||
const shortTimestamp = Date.now().toString().slice(-8)
|
||||
|
||||
return {
|
||||
id: faker.string.uuid(),
|
||||
shortId: `h${shortTimestamp}${faker.string.alphanumeric(2)}`,
|
||||
quote: faker.lorem.sentence(),
|
||||
prefix: faker.lorem.words(3),
|
||||
suffix: faker.lorem.words(3),
|
||||
highlightPositionPercent: faker.number.int({ min: 10, max: 90 }),
|
||||
highlightPositionAnchorIndex: faker.number.int({ min: 0, max: 100 }),
|
||||
color: 'yellow',
|
||||
highlightType: HighlightType.HIGHLIGHT,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
// These will be set by the caller
|
||||
libraryItemId: '', // Must be provided
|
||||
userId: '', // Must be provided
|
||||
libraryItem: undefined,
|
||||
user: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
protected getRepository(): Repository<HighlightEntity> {
|
||||
return getTestRepository(HighlightEntity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a highlight with a specific color
|
||||
*/
|
||||
async withColor(
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
color: string,
|
||||
overrides: Partial<HighlightEntity> = {},
|
||||
): Promise<HighlightEntity> {
|
||||
return this.create({
|
||||
libraryItemId,
|
||||
userId,
|
||||
color,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a highlight with annotation
|
||||
*/
|
||||
async withAnnotation(
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
annotation: string,
|
||||
overrides: Partial<HighlightEntity> = {},
|
||||
): Promise<HighlightEntity> {
|
||||
return this.create({
|
||||
libraryItemId,
|
||||
userId,
|
||||
annotation,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a redacted highlight
|
||||
*/
|
||||
async redacted(
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
overrides: Partial<HighlightEntity> = {},
|
||||
): Promise<HighlightEntity> {
|
||||
return this.create({
|
||||
libraryItemId,
|
||||
userId,
|
||||
highlightType: HighlightType.REDACTION,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple highlights for an article
|
||||
*/
|
||||
async createManyForArticle(
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
count: number,
|
||||
): Promise<HighlightEntity[]> {
|
||||
const highlights: HighlightEntity[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
highlights.push(
|
||||
await this.create({
|
||||
libraryItemId,
|
||||
userId,
|
||||
highlightPositionPercent: (i + 1) * (100 / (count + 1)),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return highlights
|
||||
}
|
||||
|
||||
/**
|
||||
* Build highlight with color (in memory)
|
||||
*/
|
||||
buildWithColor(
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
color: string,
|
||||
overrides: Partial<HighlightEntity> = {},
|
||||
): HighlightEntity {
|
||||
return this.build({
|
||||
libraryItemId,
|
||||
userId,
|
||||
color,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const HighlightFactory = new HighlightFactoryClass()
|
||||
26
packages/api-nest/test/factories/index.ts
Normal file
26
packages/api-nest/test/factories/index.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Test Data Factories
|
||||
*
|
||||
* Provides easy-to-use factories for generating test data with Faker.js
|
||||
* Supports both in-memory entity creation (build) and database persistence (create).
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { UserFactory, LibraryItemFactory } from './factories'
|
||||
*
|
||||
* // Create test user in database
|
||||
* const user = await UserFactory.create({ email: 'test@example.com' })
|
||||
*
|
||||
* // Create test library item
|
||||
* const item = await LibraryItemFactory.create({
|
||||
* userId: user.id,
|
||||
* title: 'Test Article'
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
|
||||
export { UserFactory } from './user.factory'
|
||||
export { LibraryItemFactory } from './library-item.factory'
|
||||
export { HighlightFactory } from './highlight.factory'
|
||||
export { LabelFactory } from './label.factory'
|
||||
export { BaseFactory } from './base.factory'
|
||||
118
packages/api-nest/test/factories/label.factory.ts
Normal file
118
packages/api-nest/test/factories/label.factory.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { faker } from '@faker-js/faker'
|
||||
import { Repository } from 'typeorm'
|
||||
import { Label } from '../../src/label/entities/label.entity'
|
||||
import { BaseFactory, getTestRepository } from './base.factory'
|
||||
|
||||
/**
|
||||
* LabelFactory - Generate test labels
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a label
|
||||
* const label = await LabelFactory.create({
|
||||
* userId: user.id,
|
||||
* name: 'Important'
|
||||
* })
|
||||
*
|
||||
* // Use helper methods
|
||||
* const internalLabel = await LabelFactory.internal(user.id, 'RSS')
|
||||
* ```
|
||||
*/
|
||||
class LabelFactoryClass extends BaseFactory<Label> {
|
||||
private static labelColors = [
|
||||
'#FF5733',
|
||||
'#33FF57',
|
||||
'#3357FF',
|
||||
'#FF33F5',
|
||||
'#F5FF33',
|
||||
'#33FFF5',
|
||||
]
|
||||
|
||||
protected generateDefaults() {
|
||||
return {
|
||||
id: faker.string.uuid(),
|
||||
name: faker.word.adjective() + '-' + faker.word.noun(),
|
||||
color: faker.helpers.arrayElement(LabelFactoryClass.labelColors),
|
||||
description: faker.lorem.sentence(),
|
||||
position: faker.number.int({ min: 0, max: 100 }),
|
||||
internal: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
// Must be set by caller
|
||||
userId: '', // Must be provided
|
||||
user: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
protected getRepository(): Repository<Label> {
|
||||
return getTestRepository(Label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an internal label (system-created, not deletable)
|
||||
*/
|
||||
async internal(
|
||||
userId: string,
|
||||
name: string,
|
||||
overrides: Partial<Label> = {},
|
||||
): Promise<Label> {
|
||||
return this.create({
|
||||
userId,
|
||||
name,
|
||||
internal: true,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a label with a specific color
|
||||
*/
|
||||
async withColor(
|
||||
userId: string,
|
||||
color: string,
|
||||
overrides: Partial<Label> = {},
|
||||
): Promise<Label> {
|
||||
return this.create({
|
||||
userId,
|
||||
color,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple labels for a user
|
||||
*/
|
||||
async createManyForUser(userId: string, count: number): Promise<Label[]> {
|
||||
const labels: Label[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
labels.push(
|
||||
await this.create({
|
||||
userId,
|
||||
position: i,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
/**
|
||||
* Build internal label (in memory)
|
||||
*/
|
||||
buildInternal(
|
||||
userId: string,
|
||||
name: string,
|
||||
overrides: Partial<Label> = {},
|
||||
): Label {
|
||||
return this.build({
|
||||
userId,
|
||||
name,
|
||||
internal: true,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const LabelFactory = new LabelFactoryClass()
|
||||
168
packages/api-nest/test/factories/library-item.factory.ts
Normal file
168
packages/api-nest/test/factories/library-item.factory.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { faker } from '@faker-js/faker'
|
||||
import { Repository } from 'typeorm'
|
||||
import {
|
||||
LibraryItemEntity,
|
||||
LibraryItemState,
|
||||
ContentReaderType,
|
||||
} from '../../src/library/entities/library-item.entity'
|
||||
import { BaseFactory, getTestRepository } from './base.factory'
|
||||
import { FOLDERS } from '../../src/constants/folders.constants'
|
||||
|
||||
/**
|
||||
* LibraryItemFactory - Generate test library items
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a library item
|
||||
* const item = await LibraryItemFactory.create({
|
||||
* userId: user.id,
|
||||
* title: 'Test Article'
|
||||
* })
|
||||
*
|
||||
* // Use helper methods
|
||||
* const archivedItem = await LibraryItemFactory.archived(user.id)
|
||||
* const deletedItem = await LibraryItemFactory.deleted(user.id)
|
||||
* ```
|
||||
*/
|
||||
class LibraryItemFactoryClass extends BaseFactory<LibraryItemEntity> {
|
||||
protected generateDefaults() {
|
||||
const timestamp = Date.now()
|
||||
const title = faker.lorem.sentence()
|
||||
|
||||
return {
|
||||
id: faker.string.uuid(),
|
||||
title,
|
||||
slug: faker.helpers.slugify(title).toLowerCase() + `-${timestamp}`,
|
||||
originalUrl: faker.internet.url(),
|
||||
savedAt: new Date(),
|
||||
state: LibraryItemState.SUCCEEDED,
|
||||
folder: FOLDERS.INBOX,
|
||||
contentReader: ContentReaderType.WEB,
|
||||
itemType: 'ARTICLE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
// These will be set by the caller
|
||||
userId: '', // Must be provided
|
||||
user: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
protected getRepository(): Repository<LibraryItemEntity> {
|
||||
return getTestRepository(LibraryItemEntity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an archived library item
|
||||
*/
|
||||
async archived(userId: string, overrides: Partial<LibraryItemEntity> = {}): Promise<LibraryItemEntity> {
|
||||
return this.create({
|
||||
userId,
|
||||
folder: FOLDERS.ARCHIVE,
|
||||
state: LibraryItemState.ARCHIVED,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deleted library item (in trash)
|
||||
*/
|
||||
async deleted(userId: string, overrides: Partial<LibraryItemEntity> = {}): Promise<LibraryItemEntity> {
|
||||
return this.create({
|
||||
userId,
|
||||
folder: FOLDERS.TRASH,
|
||||
state: LibraryItemState.DELETED,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an item with reading progress
|
||||
*/
|
||||
async withProgress(
|
||||
userId: string,
|
||||
percentComplete: number,
|
||||
overrides: Partial<LibraryItemEntity> = {},
|
||||
): Promise<LibraryItemEntity> {
|
||||
const readAt = percentComplete === 100 ? new Date() : null
|
||||
|
||||
return this.create({
|
||||
userId,
|
||||
readingProgressTopPercent: percentComplete,
|
||||
readingProgressBottomPercent: Math.min(percentComplete + 5, 100),
|
||||
readAt,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an item that's still being processed
|
||||
*/
|
||||
async processing(userId: string, overrides: Partial<LibraryItemEntity> = {}): Promise<LibraryItemEntity> {
|
||||
return this.create({
|
||||
userId,
|
||||
state: LibraryItemState.CONTENT_NOT_FETCHED,
|
||||
title: faker.internet.url(), // Temporary title (URL)
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an item with a notebook
|
||||
*/
|
||||
async withNotebook(
|
||||
userId: string,
|
||||
noteContent: string,
|
||||
overrides: Partial<LibraryItemEntity> = {},
|
||||
): Promise<LibraryItemEntity> {
|
||||
return this.create({
|
||||
userId,
|
||||
note: noteContent,
|
||||
noteUpdatedAt: new Date(),
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PDF library item
|
||||
*/
|
||||
async pdf(userId: string, overrides: Partial<LibraryItemEntity> = {}): Promise<LibraryItemEntity> {
|
||||
return this.create({
|
||||
userId,
|
||||
contentReader: ContentReaderType.PDF,
|
||||
itemType: 'FILE',
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Build archived item (in memory)
|
||||
*/
|
||||
buildArchived(userId: string, overrides: Partial<LibraryItemEntity> = {}): LibraryItemEntity {
|
||||
return this.build({
|
||||
userId,
|
||||
folder: FOLDERS.ARCHIVE,
|
||||
state: LibraryItemState.ARCHIVED,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Build item with progress (in memory)
|
||||
*/
|
||||
buildWithProgress(
|
||||
userId: string,
|
||||
percentComplete: number,
|
||||
overrides: Partial<LibraryItemEntity> = {},
|
||||
): LibraryItemEntity {
|
||||
return this.build({
|
||||
userId,
|
||||
readingProgressTopPercent: percentComplete,
|
||||
readingProgressBottomPercent: Math.min(percentComplete + 5, 100),
|
||||
readAt: percentComplete === 100 ? new Date() : null,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const LibraryItemFactory = new LibraryItemFactoryClass()
|
||||
113
packages/api-nest/test/factories/user.factory.ts
Normal file
113
packages/api-nest/test/factories/user.factory.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { faker } from '@faker-js/faker'
|
||||
import { Repository } from 'typeorm'
|
||||
import { User, StatusType, RegistrationType } from '../../src/user/entities/user.entity'
|
||||
import { UserRole } from '../../src/user/enums/user-role.enum'
|
||||
import { BaseFactory, getTestRepository } from './base.factory'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
/**
|
||||
* UserFactory - Generate test user data
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a user in the database
|
||||
* const user = await UserFactory.create({ email: 'test@example.com' })
|
||||
*
|
||||
* // Build a user in memory (for mocking)
|
||||
* const user = UserFactory.build({ role: 'admin' })
|
||||
*
|
||||
* // Use helper methods
|
||||
* const admin = await UserFactory.admin()
|
||||
* const pendingUser = await UserFactory.pending()
|
||||
* ```
|
||||
*/
|
||||
class UserFactoryClass extends BaseFactory<User> {
|
||||
protected generateDefaults() {
|
||||
const firstName = faker.person.firstName()
|
||||
const lastName = faker.person.lastName()
|
||||
|
||||
return {
|
||||
id: faker.string.uuid(),
|
||||
sourceUserId: faker.string.uuid(), // Required unique identifier
|
||||
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
|
||||
name: `${firstName} ${lastName}`,
|
||||
password: bcrypt.hashSync('password123', 10), // Default password
|
||||
role: UserRole.USER,
|
||||
status: StatusType.ACTIVE,
|
||||
source: RegistrationType.EMAIL,
|
||||
membership: 'REGULAR',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
protected getRepository(): Repository<User> {
|
||||
return getTestRepository(User)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an admin user
|
||||
*/
|
||||
async admin(overrides: Partial<User> = {}): Promise<User> {
|
||||
return this.create({
|
||||
role: UserRole.ADMIN,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pending user (email not yet verified)
|
||||
*/
|
||||
async pending(overrides: Partial<User> = {}): Promise<User> {
|
||||
return this.create({
|
||||
status: StatusType.PENDING,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an archived user
|
||||
*/
|
||||
async archived(overrides: Partial<User> = {}): Promise<User> {
|
||||
return this.create({
|
||||
status: StatusType.ARCHIVED,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with a specific password (for login tests)
|
||||
*/
|
||||
async withPassword(
|
||||
password: string,
|
||||
overrides: Partial<User> = {},
|
||||
): Promise<User> {
|
||||
return this.create({
|
||||
password: bcrypt.hashSync(password, 10),
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Build admin user (in memory, not saved)
|
||||
*/
|
||||
buildAdmin(overrides: Partial<User> = {}): User {
|
||||
return this.build({
|
||||
role: UserRole.ADMIN,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pending user (in memory, not saved)
|
||||
*/
|
||||
buildPending(overrides: Partial<User> = {}): User {
|
||||
return this.build({
|
||||
status: StatusType.PENDING,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const UserFactory = new UserFactoryClass()
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(bullmq|msgpackr)/)"
|
||||
"node_modules/(?!(bullmq|msgpackr|@faker-js)/)"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
|
|
@ -17,5 +17,9 @@
|
|||
"moduleNameMapper": {
|
||||
"^typeorm$": "<rootDir>/../../../node_modules/typeorm",
|
||||
"^uuid$": "<rootDir>/../../../node_modules/uuid/dist/index.js"
|
||||
}
|
||||
},
|
||||
"globalSetup": "<rootDir>/setup/global-setup.ts",
|
||||
"globalTeardown": "<rootDir>/setup/global-teardown.ts",
|
||||
"setupFilesAfterEnv": ["<rootDir>/setup/jest-environment-setup.ts"],
|
||||
"testTimeout": 60000
|
||||
}
|
||||
|
|
|
|||
33
packages/api-nest/test/setup/global-setup.ts
Normal file
33
packages/api-nest/test/setup/global-setup.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { setupTestContainer } from './testcontainers'
|
||||
|
||||
/**
|
||||
* Jest Global Setup
|
||||
* Runs once before all test suites
|
||||
* Starts PostgreSQL container and initializes database
|
||||
*/
|
||||
export default async function globalSetup() {
|
||||
console.log('\n🚀 Jest Global Setup - Starting test infrastructure...\n')
|
||||
|
||||
try {
|
||||
// Start container and initialize database
|
||||
const { container, dataSource } = await setupTestContainer()
|
||||
|
||||
// Store connection details in environment variables for test workers
|
||||
process.env.TEST_DB_HOST = container.getHost()
|
||||
process.env.TEST_DB_PORT = container.getPort().toString()
|
||||
process.env.TEST_DB_DATABASE = container.getDatabase()
|
||||
process.env.TEST_DB_USERNAME = container.getUsername()
|
||||
process.env.TEST_DB_PASSWORD = container.getPassword()
|
||||
|
||||
// Store for global teardown
|
||||
// @ts-ignore - globalThis extension
|
||||
globalThis.__TEST_CONTAINER__ = container
|
||||
// @ts-ignore
|
||||
globalThis.__TEST_DATASOURCE__ = dataSource
|
||||
|
||||
console.log('\n✅ Global setup complete!\n')
|
||||
} catch (error) {
|
||||
console.error('❌ Global setup failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
23
packages/api-nest/test/setup/global-teardown.ts
Normal file
23
packages/api-nest/test/setup/global-teardown.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { teardownTestContainer } from './testcontainers'
|
||||
|
||||
/**
|
||||
* Jest Global Teardown
|
||||
* Runs once after all test suites complete
|
||||
* Stops PostgreSQL container and cleans up resources
|
||||
*/
|
||||
export default async function globalTeardown() {
|
||||
console.log('\n🧹 Jest Global Teardown - Cleaning up test infrastructure...\n')
|
||||
|
||||
try {
|
||||
await teardownTestContainer()
|
||||
|
||||
// Clean up global reference
|
||||
// @ts-ignore - globalThis extension
|
||||
delete globalThis.__TEST_DATASOURCE__
|
||||
|
||||
console.log('\n✅ Global teardown complete!\n')
|
||||
} catch (error) {
|
||||
console.error('❌ Global teardown failed:', error)
|
||||
// Don't throw - let tests complete even if cleanup fails
|
||||
}
|
||||
}
|
||||
81
packages/api-nest/test/setup/jest-environment-setup.ts
Normal file
81
packages/api-nest/test/setup/jest-environment-setup.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Jest Environment Setup
|
||||
* Runs in each test worker to initialize the test DataSource
|
||||
*/
|
||||
|
||||
import { DataSource } from 'typeorm'
|
||||
import { User } from '../../src/user/entities/user.entity'
|
||||
import { UserProfile } from '../../src/user/entities/profile.entity'
|
||||
import { UserPersonalization } from '../../src/user/entities/user-personalization.entity'
|
||||
import { Filter } from '../../src/filter/entities/filter.entity'
|
||||
import { Group } from '../../src/group/entities/group.entity'
|
||||
import { Invite } from '../../src/group/entities/invite.entity'
|
||||
import { GroupMembership } from '../../src/group/entities/group-membership.entity'
|
||||
import { LibraryItemEntity } from '../../src/library/entities/library-item.entity'
|
||||
import { Label } from '../../src/label/entities/label.entity'
|
||||
import { EntityLabel } from '../../src/label/entities/entity-label.entity'
|
||||
import { HighlightEntity } from '../../src/highlight/entities/highlight.entity'
|
||||
|
||||
/**
|
||||
* Initialize test DataSource using connection details from global setup
|
||||
* This runs once per test worker
|
||||
*/
|
||||
async function initializeTestDataSource() {
|
||||
// Get connection details set by global setup
|
||||
const host = process.env.TEST_DB_HOST
|
||||
const port = parseInt(process.env.TEST_DB_PORT || '5432')
|
||||
const database = process.env.TEST_DB_DATABASE
|
||||
const username = process.env.TEST_DB_USERNAME
|
||||
const password = process.env.TEST_DB_PASSWORD
|
||||
|
||||
if (!host || !database || !username || !password) {
|
||||
throw new Error('Test database connection details not found. Make sure globalSetup ran successfully.')
|
||||
}
|
||||
|
||||
// Create and initialize DataSource
|
||||
const dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
host,
|
||||
port,
|
||||
database,
|
||||
username,
|
||||
password,
|
||||
entities: [
|
||||
User,
|
||||
UserProfile,
|
||||
UserPersonalization,
|
||||
Filter,
|
||||
Group,
|
||||
Invite,
|
||||
GroupMembership,
|
||||
LibraryItemEntity,
|
||||
Label,
|
||||
EntityLabel,
|
||||
HighlightEntity,
|
||||
],
|
||||
synchronize: false, // Schema already created by global setup
|
||||
logging: false,
|
||||
})
|
||||
|
||||
await dataSource.initialize()
|
||||
|
||||
// Store in globalThis for factories to access
|
||||
// @ts-ignore
|
||||
globalThis.__TEST_DATASOURCE__ = dataSource
|
||||
|
||||
return dataSource
|
||||
}
|
||||
|
||||
// Initialize immediately when this file is loaded
|
||||
beforeAll(async () => {
|
||||
await initializeTestDataSource()
|
||||
})
|
||||
|
||||
// Clean up after all tests in this worker
|
||||
afterAll(async () => {
|
||||
// @ts-ignore
|
||||
const dataSource = globalThis.__TEST_DATASOURCE__ as DataSource
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy()
|
||||
}
|
||||
})
|
||||
20
packages/api-nest/test/setup/test-datasource.ts
Normal file
20
packages/api-nest/test/setup/test-datasource.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { DataSource } from 'typeorm'
|
||||
|
||||
/**
|
||||
* Get the current test DataSource instance
|
||||
* This is set by the global setup and used by factories
|
||||
*
|
||||
* This file does NOT import testcontainers, avoiding ESM import issues in Jest
|
||||
*/
|
||||
export function getTestDataSource(): DataSource {
|
||||
// @ts-ignore - DataSource is set by global setup
|
||||
const dataSource = globalThis.__TEST_DATASOURCE__ as DataSource
|
||||
|
||||
if (!dataSource || !dataSource.isInitialized) {
|
||||
throw new Error(
|
||||
'Test DataSource not initialized. Make sure globalSetup has run.',
|
||||
)
|
||||
}
|
||||
|
||||
return dataSource
|
||||
}
|
||||
160
packages/api-nest/test/setup/testcontainers.ts
Normal file
160
packages/api-nest/test/setup/testcontainers.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { User } from '../../src/user/entities/user.entity'
|
||||
import { UserProfile } from '../../src/user/entities/profile.entity'
|
||||
import { UserPersonalization } from '../../src/user/entities/user-personalization.entity'
|
||||
import { Filter } from '../../src/filter/entities/filter.entity'
|
||||
import { Group } from '../../src/group/entities/group.entity'
|
||||
import { Invite } from '../../src/group/entities/invite.entity'
|
||||
import { GroupMembership } from '../../src/group/entities/group-membership.entity'
|
||||
import { LibraryItemEntity } from '../../src/library/entities/library-item.entity'
|
||||
import { Label } from '../../src/label/entities/label.entity'
|
||||
import { EntityLabel } from '../../src/label/entities/entity-label.entity'
|
||||
import { HighlightEntity } from '../../src/highlight/entities/highlight.entity'
|
||||
|
||||
let container: StartedPostgreSqlContainer | null = null
|
||||
let dataSource: DataSource | null = null
|
||||
|
||||
/**
|
||||
* Start PostgreSQL container and initialize DataSource with migrations
|
||||
* This is called once at the start of the entire test run
|
||||
*/
|
||||
export async function setupTestContainer(): Promise<{
|
||||
container: StartedPostgreSqlContainer
|
||||
dataSource: DataSource
|
||||
}> {
|
||||
console.log('🐳 Starting PostgreSQL test container...')
|
||||
|
||||
// Start PostgreSQL 15 container
|
||||
container = await new PostgreSqlContainer('postgres:15-alpine')
|
||||
.withDatabase('test_omnivore')
|
||||
.withUsername('test_user')
|
||||
.withPassword('test_password')
|
||||
.withExposedPorts(5432)
|
||||
.start()
|
||||
|
||||
console.log(`✅ PostgreSQL container started on port ${container.getPort()}`)
|
||||
|
||||
// Create DataSource with all entities
|
||||
dataSource = new DataSource({
|
||||
type: 'postgres',
|
||||
host: container.getHost(),
|
||||
port: container.getPort(),
|
||||
database: container.getDatabase(),
|
||||
username: container.getUsername(),
|
||||
password: container.getPassword(),
|
||||
entities: [
|
||||
User,
|
||||
UserProfile,
|
||||
UserPersonalization,
|
||||
Filter,
|
||||
Group,
|
||||
Invite,
|
||||
GroupMembership,
|
||||
LibraryItemEntity,
|
||||
Label,
|
||||
EntityLabel,
|
||||
HighlightEntity,
|
||||
],
|
||||
synchronize: false, // We'll call synchronize() manually after creating schema
|
||||
logging: false, // Disable logging for cleaner test output
|
||||
dropSchema: false, // Don't drop schema (we want it to persist across tests)
|
||||
})
|
||||
|
||||
console.log('🔌 Initializing database connection...')
|
||||
await dataSource.initialize()
|
||||
|
||||
console.log('📁 Creating omnivore schema...')
|
||||
// Create the omnivore schema that our entities use
|
||||
await dataSource.query('CREATE SCHEMA IF NOT EXISTS omnivore')
|
||||
|
||||
console.log('🔄 Synchronizing database schema...')
|
||||
// Now synchronize will create tables in the omnivore schema
|
||||
await dataSource.synchronize()
|
||||
|
||||
console.log('✅ Test database ready!')
|
||||
|
||||
return { container, dataSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop PostgreSQL container and close DataSource
|
||||
* This is called once at the end of the entire test run
|
||||
*/
|
||||
export async function teardownTestContainer(): Promise<void> {
|
||||
console.log('🧹 Cleaning up test container...')
|
||||
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy()
|
||||
console.log('✅ DataSource closed')
|
||||
}
|
||||
|
||||
if (container) {
|
||||
await container.stop()
|
||||
console.log('✅ PostgreSQL container stopped')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current DataSource instance
|
||||
* Used by factories and test helpers
|
||||
*/
|
||||
export function getTestDataSource(): DataSource {
|
||||
if (!dataSource || !dataSource.isInitialized) {
|
||||
throw new Error(
|
||||
'Test DataSource not initialized. Make sure globalSetup has run.',
|
||||
)
|
||||
}
|
||||
return dataSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a transaction for test isolation
|
||||
* Call this in beforeEach hooks
|
||||
*/
|
||||
export async function beginTestTransaction(): Promise<void> {
|
||||
const ds = getTestDataSource()
|
||||
await ds.query('BEGIN')
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback transaction to restore database state
|
||||
* Call this in afterEach hooks
|
||||
*/
|
||||
export async function rollbackTestTransaction(): Promise<void> {
|
||||
const ds = getTestDataSource()
|
||||
await ds.query('ROLLBACK')
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean all tables (alternative to transaction rollback)
|
||||
* Useful for E2E tests that need to commit transactions
|
||||
*/
|
||||
export async function cleanDatabase(): Promise<void> {
|
||||
const ds = getTestDataSource()
|
||||
|
||||
// Disable foreign key checks temporarily
|
||||
await ds.query('SET session_replication_role = replica')
|
||||
|
||||
// Truncate all tables
|
||||
const tables = [
|
||||
'omnivore.entity_label',
|
||||
'omnivore.highlight',
|
||||
'omnivore.label',
|
||||
'omnivore.library_item',
|
||||
'omnivore.group_membership',
|
||||
'omnivore.invite',
|
||||
'omnivore.group',
|
||||
'omnivore.filter',
|
||||
'omnivore.user_personalization',
|
||||
'omnivore.user_profile',
|
||||
'omnivore.user',
|
||||
]
|
||||
|
||||
for (const table of tables) {
|
||||
await ds.query(`TRUNCATE TABLE ${table} CASCADE`)
|
||||
}
|
||||
|
||||
// Re-enable foreign key checks
|
||||
await ds.query('SET session_replication_role = DEFAULT')
|
||||
}
|
||||
226
yarn.lock
226
yarn.lock
|
|
@ -2276,6 +2276,11 @@
|
|||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
|
||||
"@balena/dockerignore@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
|
||||
integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==
|
||||
|
||||
"@base2/pretty-print-object@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz"
|
||||
|
|
@ -2749,6 +2754,11 @@
|
|||
"@eslint/core" "^0.15.2"
|
||||
levn "^0.4.1"
|
||||
|
||||
"@faker-js/faker@^10.1.0":
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-10.1.0.tgz#eb72869d01ccbff41a77aa7ac851ce1ac9371129"
|
||||
integrity sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==
|
||||
|
||||
"@fast-csv/parse@^5.0.0":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.npmjs.org/@fast-csv/parse/-/parse-5.0.5.tgz"
|
||||
|
|
@ -3582,6 +3592,14 @@
|
|||
"@grpc/proto-loader" "^0.7.13"
|
||||
"@js-sdsl/ordered-map" "^4.4.2"
|
||||
|
||||
"@grpc/grpc-js@^1.11.1":
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.0.tgz#a3c47e7816ca2b4d5490cba9e06a3cf324e675ad"
|
||||
integrity sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==
|
||||
dependencies:
|
||||
"@grpc/proto-loader" "^0.8.0"
|
||||
"@js-sdsl/ordered-map" "^4.4.2"
|
||||
|
||||
"@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.13":
|
||||
version "0.7.15"
|
||||
resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz"
|
||||
|
|
@ -8918,6 +8936,13 @@
|
|||
dependencies:
|
||||
"@tanstack/query-core" "5.90.2"
|
||||
|
||||
"@testcontainers/postgresql@^11.7.1":
|
||||
version "11.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-11.7.1.tgz#707a39f618528da7c5eb96ea899c538d56e1ec97"
|
||||
integrity sha512-8PfGNqwdyoMPQuubZM0wd07/tfi4vhLAjXP791tM105vSCmzCOhLfYu2CIq04GKVlmW1J5z5nOZWLNlU9WrUuQ==
|
||||
dependencies:
|
||||
testcontainers "^11.7.1"
|
||||
|
||||
"@testing-library/cypress@^8.0.2":
|
||||
version "8.0.7"
|
||||
resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-8.0.7.tgz"
|
||||
|
|
@ -9295,6 +9320,23 @@
|
|||
resolved "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz"
|
||||
integrity sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==
|
||||
|
||||
"@types/docker-modem@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/docker-modem/-/docker-modem-3.0.6.tgz#1f9262fcf85425b158ca725699a03eb23cddbf87"
|
||||
integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/ssh2" "*"
|
||||
|
||||
"@types/dockerode@^3.3.44":
|
||||
version "3.3.44"
|
||||
resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.44.tgz#1e6d5b291646820e9daabfa132cdb33c9d535b56"
|
||||
integrity sha512-fUpIHlsbYpxAJb285xx3vp7q5wf5mjqSn3cYwl/MhiM+DB99OdO5sOCPlO0PjO+TyOtphPs7tMVLU/RtOo/JjA==
|
||||
dependencies:
|
||||
"@types/docker-modem" "*"
|
||||
"@types/node" "*"
|
||||
"@types/ssh2" "*"
|
||||
|
||||
"@types/dompurify@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz"
|
||||
|
|
@ -10180,6 +10222,28 @@
|
|||
resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz"
|
||||
integrity sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==
|
||||
|
||||
"@types/ssh2-streams@*":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
|
||||
integrity sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ssh2@*":
|
||||
version "1.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.5.tgz#6d8f45db2f39519b8d9377268fa71ed77d969686"
|
||||
integrity sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==
|
||||
dependencies:
|
||||
"@types/node" "^18.11.18"
|
||||
|
||||
"@types/ssh2@^0.5.48":
|
||||
version "0.5.52"
|
||||
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741"
|
||||
integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/ssh2-streams" "*"
|
||||
|
||||
"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz"
|
||||
|
|
@ -12195,7 +12259,7 @@ asn1.js@^5.3.0:
|
|||
minimalistic-assert "^1.0.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
asn1@^0.2.6, asn1@~0.2.3:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz"
|
||||
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
|
||||
|
|
@ -12278,6 +12342,11 @@ async-function@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
|
||||
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
|
||||
|
||||
async-lock@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f"
|
||||
integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==
|
||||
|
||||
async-retry@^1.2.1, async-retry@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz"
|
||||
|
|
@ -12750,7 +12819,7 @@ batch@0.6.1:
|
|||
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
||||
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz"
|
||||
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
|
||||
|
|
@ -13212,6 +13281,11 @@ bufrw@^1.2.1:
|
|||
hexer "^1.5.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
buildcheck@~0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
|
||||
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
|
||||
|
||||
builtin-status-codes@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz"
|
||||
|
|
@ -13262,6 +13336,11 @@ busboy@1.6.0, busboy@^1.6.0:
|
|||
dependencies:
|
||||
streamsearch "^1.1.0"
|
||||
|
||||
byline@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
|
||||
integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==
|
||||
|
||||
byte-size@8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.npmjs.org/byte-size/-/byte-size-8.1.1.tgz"
|
||||
|
|
@ -14853,6 +14932,14 @@ cp-file@^7.0.0:
|
|||
nested-error-stacks "^2.0.0"
|
||||
p-event "^4.1.0"
|
||||
|
||||
cpu-features@~0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5"
|
||||
integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==
|
||||
dependencies:
|
||||
buildcheck "~0.0.6"
|
||||
nan "^2.19.0"
|
||||
|
||||
cpy@^8.1.2:
|
||||
version "8.1.2"
|
||||
resolved "https://registry.npmjs.org/cpy/-/cpy-8.1.2.tgz"
|
||||
|
|
@ -15367,6 +15454,13 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.2.7:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debuglog@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz"
|
||||
|
|
@ -15824,6 +15918,36 @@ dns-packet@^5.2.2:
|
|||
dependencies:
|
||||
"@leichtgewicht/ip-codec" "^2.0.1"
|
||||
|
||||
docker-compose@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-1.3.0.tgz#6da4bb9d542b4cce6474aa3146a909eda5d23623"
|
||||
integrity sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==
|
||||
dependencies:
|
||||
yaml "^2.2.2"
|
||||
|
||||
docker-modem@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-5.0.6.tgz#cbe9d86a1fe66d7a072ac7fb99a9fc390a3e8b9a"
|
||||
integrity sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
readable-stream "^3.5.0"
|
||||
split-ca "^1.0.1"
|
||||
ssh2 "^1.15.0"
|
||||
|
||||
dockerode@^4.0.8:
|
||||
version "4.0.9"
|
||||
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.9.tgz#15b32000edad25520be6fafa9ad6bc4529b06be7"
|
||||
integrity sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==
|
||||
dependencies:
|
||||
"@balena/dockerignore" "^1.0.2"
|
||||
"@grpc/grpc-js" "^1.11.1"
|
||||
"@grpc/proto-loader" "^0.7.13"
|
||||
docker-modem "^5.0.6"
|
||||
protobufjs "^7.3.2"
|
||||
tar-fs "^2.1.4"
|
||||
uuid "^10.0.0"
|
||||
|
||||
doctrine@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz"
|
||||
|
|
@ -18393,6 +18517,11 @@ get-port@5.1.1:
|
|||
resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz"
|
||||
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
|
||||
|
||||
get-port@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-7.1.0.tgz#d5a500ebfc7aa705294ec2b83cc38c5d0e364fec"
|
||||
integrity sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==
|
||||
|
||||
get-proto@^1.0.0, get-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
|
||||
|
|
@ -24858,7 +24987,7 @@ mute-stream@1.0.0, mute-stream@^1.0.0, mute-stream@~1.0.0:
|
|||
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz"
|
||||
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
|
||||
|
||||
nan@^2.12.1:
|
||||
nan@^2.12.1, nan@^2.19.0, nan@^2.23.0:
|
||||
version "2.23.0"
|
||||
resolved "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz"
|
||||
integrity sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==
|
||||
|
|
@ -27626,6 +27755,22 @@ propagate@^2.0.0:
|
|||
resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz"
|
||||
integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
|
||||
|
||||
proper-lockfile@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
|
||||
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
retry "^0.12.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
properties-reader@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/properties-reader/-/properties-reader-2.3.0.tgz#f3ab84224c9535a7a36e011ae489a79a13b472b2"
|
||||
integrity sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==
|
||||
dependencies:
|
||||
mkdirp "^1.0.4"
|
||||
|
||||
property-expr@^2.0.4:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz"
|
||||
|
|
@ -30693,6 +30838,11 @@ spdy@^4.0.2:
|
|||
select-hose "^2.0.0"
|
||||
spdy-transport "^3.0.0"
|
||||
|
||||
split-ca@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
|
||||
integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==
|
||||
|
||||
split-string@^3.0.1, split-string@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz"
|
||||
|
|
@ -30748,6 +30898,25 @@ sql-highlight@^6.0.0:
|
|||
resolved "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz"
|
||||
integrity sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==
|
||||
|
||||
ssh-remote-port-forward@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz#72b0c5df8ec27ca300c75805cc6b266dee07e298"
|
||||
integrity sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==
|
||||
dependencies:
|
||||
"@types/ssh2" "^0.5.48"
|
||||
ssh2 "^1.4.0"
|
||||
|
||||
ssh2@^1.15.0, ssh2@^1.4.0:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.17.0.tgz#dc686e8e3abdbd4ad95d46fa139615903c12258c"
|
||||
integrity sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==
|
||||
dependencies:
|
||||
asn1 "^0.2.6"
|
||||
bcrypt-pbkdf "^1.0.2"
|
||||
optionalDependencies:
|
||||
cpu-features "~0.0.10"
|
||||
nan "^2.23.0"
|
||||
|
||||
sshpk@^1.14.1, sshpk@^1.7.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz"
|
||||
|
|
@ -31528,6 +31697,16 @@ tar-fs@^2.0.0:
|
|||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-fs@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930"
|
||||
integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-fs@^3.0.4, tar-fs@^3.0.6:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz"
|
||||
|
|
@ -31539,6 +31718,17 @@ tar-fs@^3.0.4, tar-fs@^3.0.6:
|
|||
bare-fs "^4.0.1"
|
||||
bare-path "^3.0.0"
|
||||
|
||||
tar-fs@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.1.1.tgz#4f164e59fb60f103d472360731e8c6bb4a7fe9ef"
|
||||
integrity sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^3.1.5"
|
||||
optionalDependencies:
|
||||
bare-fs "^4.0.1"
|
||||
bare-path "^3.0.0"
|
||||
|
||||
tar-stream@^2.1.4, tar-stream@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz"
|
||||
|
|
@ -31723,6 +31913,27 @@ test-exclude@^6.0.0:
|
|||
glob "^7.1.4"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
testcontainers@^11.7.1:
|
||||
version "11.7.1"
|
||||
resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-11.7.1.tgz#4ee8595f02b594012fae10433e059608a4aaa565"
|
||||
integrity sha512-fjut+07G4Avp6Lly/6hQePpUpQFv9ZyQd+7JC5iCDKg+dWa2Sw7fXD3pBrkzslYFfKqGx9M6kyIaLpg9VeMsjw==
|
||||
dependencies:
|
||||
"@balena/dockerignore" "^1.0.2"
|
||||
"@types/dockerode" "^3.3.44"
|
||||
archiver "^7.0.1"
|
||||
async-lock "^1.4.1"
|
||||
byline "^5.0.0"
|
||||
debug "^4.4.3"
|
||||
docker-compose "^1.3.0"
|
||||
dockerode "^4.0.8"
|
||||
get-port "^7.1.0"
|
||||
proper-lockfile "^4.1.2"
|
||||
properties-reader "^2.3.0"
|
||||
ssh-remote-port-forward "^1.0.4"
|
||||
tar-fs "^3.1.1"
|
||||
tmp "^0.2.5"
|
||||
undici "^7.16.0"
|
||||
|
||||
text-decoder@^1.1.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz"
|
||||
|
|
@ -31919,7 +32130,7 @@ tmp@^0.0.33:
|
|||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
tmp@~0.2.1:
|
||||
tmp@^0.2.5, tmp@~0.2.1:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz"
|
||||
integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==
|
||||
|
|
@ -32655,6 +32866,11 @@ undici-types@~7.10.0:
|
|||
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz"
|
||||
integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
|
||||
|
||||
undici@^7.16.0:
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/undici/-/undici-7.16.0.tgz#cb2a1e957726d458b536e3f076bf51f066901c1a"
|
||||
integrity sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==
|
||||
|
||||
unfetch@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz"
|
||||
|
|
@ -34311,7 +34527,7 @@ yaml@^1.10.0, yaml@^1.7.2:
|
|||
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yaml@^2.2.1, yaml@^2.7.1:
|
||||
yaml@^2.2.1, yaml@^2.2.2, yaml@^2.7.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz"
|
||||
integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
|
||||
|
|
|
|||
Loading…
Reference in a new issue