Bridge Training

Factories

The (faker, overrides) factories of the training types, one per type, and the rules they follow across the monorepo.

Import from @workspace/training/factories. A factory builds a valid, random instance of a training type for tests, API mocks and dev scenarios. Every type has one factory, it lives in packages/training/src/factories/, one file per entity, and the barrel exports them all.

Convention

  • Signature (faker, overrides). The Faker instance is always a parameter, never the global one imported inside the factory: tests pass the shared faker, a scenario runner passes a seeded instance so a run replays identically. A factory that delegates to builders or other factories hands them the same instance. @faker-js/faker is a type-only import in the factory files.
  • Defaults are random. A literal default is reserved for a field whose value is the type's neutral state (isArchived: false, review: {}). A test pins through overrides only the fields its assertions read.
  • The owning package owns the factory. Consumers import it, never redeclare it. DTO factories live in @workspace/api-contract/factories and are built from these domain factories through the mappers.
  • Production code never imports /factories. A Biome rule rejects @workspace/*/factories outside tests, packages/api-mock and the app dev chunk. /testing holds fakes and mocks only and does not re-export the factories, see Testing.

The repo-wide rules, the DTO factories of @workspace/api-contract, the @workspace/bridge/factories exemption and how to add a factory are in The /factories and /testing exports.

factories.ts
// A test pins only the fields its assertions read; everything else stays random.
export function archivedGroup(faker: Faker): Group {
  return makeGroup(faker, { isArchived: true, name: "Monday beginners" });
}

export function userTheme(faker: Faker, ownerLabel: string): BoardTheme {
  return makeBoardTheme(faker, {
    type: "user_theme",
    labels: { default: ownerLabel },
  });
}

// Factories compose: a nested factory receives the same faker instance.
export function premiumMonthly(faker: Faker): PlanWithPrices {
  return makePlanWithPrices(faker, { planKey: "premium", rank: 2 });
  // one monthly price whose planId is the plan's id
}

export function premiumWithoutPrices(faker: Faker): PlanWithPrices {
  return { ...makePlan(faker, { planKey: "premium" }), prices: [] };
}

export function editorRecord(faker: Faker, code: string) {
  return makePersistableEditorBoardWrite(faker, { code });
}

Catalogue

All factories take (faker: Faker, overrides: Partial<T> = {}) and return T, except where the table says otherwise.

Accounts

FactoryReturnsNotable defaults
makePersonPersontype: "person", email derived from the random first and last name, displayName their join, country a country code, accountLanguage and appLanguage drawn from fr and en, auctionSystem from makeAuctionSystem, isActive: true, role: "user", avatarTrusted: false, handle: null, handleLocked: false, onboardedAt and createdAt in the past.
makeIdentitySnapshotCreatingIdentitySnapshotA user_ identity id, lowercase email, random names, an avatar URL, a signup source from SIGNUP_SOURCES, updatedAt in the past; no role.
makeOrganisationOrganisationtype: "organisation", a company name as displayName and organisationName, otherwise as makePerson.
makePersonOwnerPersonOwnerNames taken from overrides when given, displayName their join or null, avatarUrl: null.
makeOrganisationOwnerOrganisationOwnername from overrides or a company name, displayName the same, avatarUrl: null.

Boards and themes

FactoryReturnsNotable defaults
makeBoard(faker, options)Boardoptions is Partial<Board> without game and metadata, plus hands (compact string, default two hands AKQ2.KQ3.A54.K32 / JT98.JT9.KQJ.JT9), auction, dealer and metadata: Partial<BoardMetadata>. The game comes from createBoardGame of @workspace/bridge/factories, mode show-contract, scoring unset, empty play. origin: "registered", partnership: "NS", ownership: { kind: "user" }, supportedLanguages: ["en"], metadata sharing: "private", level: 1.
makeBoardDealBoardDealRandom hands, auction S / 1S pass 2S AP, empty play, contract 2S by South, dealer South, IMP scoring, player: "S", review: {}.
makeBoardSummaryBoardSummaryid: null, code from makeDealCode, name: "Test deal", random hands, auction and play null, scoring EXPLICIT_UNSET, vuln: "NONE", source: "app", fixed createdAt of 2024-01-01.
makeBoardThemeBoardThemeRandom type from BOARD_THEME_TYPE_VALUES, two-word fr and en labels, usageCount: 0.
makeBookmarkBookmarkRandom userId and boardId, createdAt in the past.
makeDealCode(faker)stringA random deal code of DEAL_CODE_LENGTH characters from the production SHORT_CODE_ALPHABET. No overrides: pin it in the consuming factory's overrides when the test asserts on it.
makeAuctionSystemAuctionSystemidentifier drawn from natural, strong_club, precision, sayc; sortOrder 1 to 10.
makePersistableBoardWritePersistableBoardWritecode: "TEST1234", userId: "user-uuid-1", random cards with derived cardCounts, contract: "3NT" by South with result: "9", level: 3, gatedAt fixed to 2026-01-01, gatedVersion: 1.
makePersistableEditorBoardWritePersistableEditorBoardWritecode: "EDIT1234", random cards, name: "Editor Board", no auction, play or contract.
makeDealViewAppDealViewuserId: null, random cards, name: "Test deal", scoring UNSET, vuln: "NONE", declarer and dealer South, source: "app", review: [], supportedLanguages: [].
makeEditorDealViewEditorDealViewAs makeDealView with name: "Editor deal" and source: "editor".

Groups, students, sessions

FactoryReturnsNotable defaults
makeGroupGroupTwo-word name, color from a fixed palette of eight, startDate in the past as YYYY-MM-DD, endDate: null, isWeekly: true, isArchived: false, defaultSessionTime: "14:00:00", owner: null, random studentCount and sessionCount, nextSession and nextExpectedDate null.
makeSessionPreviewSessionPreviewname as Session n, future date, boardCount 0 to 12.
makeStudentStudentInteger id, random names and email, phone present with probability 0.4, isArchived: false.
makeStudentWithGroupCountStudentWithGroupCountmakeStudent plus groupCount 0 to 5.
makeCreatedGroupSessionCreatedGroupSessionstatus: "created"; overrides exclude status and accept visibility: Partial<SessionVisibility>. Random code of 8 characters, occurrenceKey built from groupId and the date, isWeekly true with probability 0.7, empty boards and linkedCollections, visibility disabled.
makeRawCreatedGroupSessionRawCreatedGroupSessionSame as above with boardIds instead of boards.
makeRawLinkedCollectionRawLinkedCollectionboardCount falls back to boardIds.length, position: 0.
makeGroupSessionSummaryGroupSessionSummaryAccepts visibility: Partial<SessionVisibility>; visibility disabled by default.
makeGroupSessionPreviewGroupSessionPreviewFuture date, random group name and color, boardCount 0 to 12.
makePublicSessionDataPublicSessionDataCode of SESSION_CODE_LENGTH characters from SHORT_CODE_ALPHABET, random authorName, empty boards, content from makePublicContent.
makeExpectedGroupSessionExpectedGroupSessionstatus: "expected", occurrenceKey from groupId and date (both honoured from overrides).
makePublicLinkPublicLinkCode of 8 characters, visibility disabled, content from makePublicContent.
makePublicContent(faker, overrides?: PublicContent)PublicContentReturns overrides as is when given. Otherwise one greeting paragraph followed by the boardsList and downloadSection magic-var blocks.
makeSessionVisibility(overrides?)SessionVisibilityTakes no Faker: enabled: false, availableFrom and availableTo null.

Collections and sharing

FactoryReturnsNotable defaults
makeDealCollectionSummaryDealCollectionSummaryThree-word name, isArchived: false, alphanumeric code of 8, boardCount 0 to 24, visibility disabled.
makeSharingGroupSharingGroupThree-word name, description: null.
makeSharingGroupMembershipSharingGroupMembershipOwner display name and email derived from the same random names, ownerAvatarUrl: null.

Plans, subscriptions, entitlements

FactoryReturnsNotable defaults
makePlanGridPlanGridgridKey a slug with a random suffix, identical en and fr labels, a bpc_ portal configuration id.
makePlanPlanplanKey slugified from a product name, rank 1 to 9, preset diffusions_per_period: 350 and feature custom_branding, providerProductId: null, status: "active", purchasable: true.
makeNewPlanNewPlanWhat the admin writes to create a tier; the key carries a random suffix so parallel tests never collide. Random diffusions_per_period, rank 1 to 90.
makeNewPlanPriceNewPlanPriceMonthly, intervalCount: 1, priceCents 100 to 500 000, currency: "EUR", providerPriceId: null, trialDays: null.
makePlanPricePlanPriceMonthly, priceCents 100 to 10 000, a price_ provider id.
makePlanWithPricesPlanWithPricesmakePlan with one monthly price bound to the plan's id, unless overrides.prices is given.
makeSubscriptionSubscriptionstatus: "active", cus_ and sub_ provider ids, billing window from three days ago to twenty-seven days ahead, cancelAtPeriodEnd: false.
makeNewEntitlementGrantNewEntitlementGrantAn admin grant raising groups_per_user_max to 21 to 500, empty features, open since recently and closing soon, grantedBy and note null.
makeEntitlementGrantEntitlementGrantmakeNewEntitlementGrant plus id, revokedAt: null, source: "admin", subscriptionId: null, quotaPeriodStart: null.

Ads and announcements

FactoryReturnsNotable defaults
makeCreativeAdsCreativeVertical 160 by 600 with a random image URL.
makeCampaignVisualNewCampaignVisualA narrow rail visual without click destination nor alt text.
makeSponsorshipAdsSponsorshipAn open-ended campaign active since the past, matching every deal, without budget: precedence: "under_plan", priority: 0, servesBudget: null, validUntil: null, DEFAULT_ADS_SPONSORSHIP_RULES.
makeCampaignAdsCampaignmakeSponsorship plus delivered: 0 and one narrow-rail BrandAsset owned by the campaign, unless assets is given.
makeBrandAssetBrandAssetAn active account-owned asset whose dimensions are exactly those of its slot (rail-160 by default); storageKey from brandAssetStorageKey, sourceStorageKey, clickUrl and alt null. slot and owner are read from overrides before the rest.
makeAnnouncementAnnouncementA recently published news item without action nor expiry, pinned: false, fr and en title and body.
makeAnnouncementForAccountAnnouncementForAccountmakeAnnouncement plus readAt: null.

Emails

FactoryReturnsNotable defaults
makeTestEmailMessageTestEmailMessageA faker recipient, locale fr or en, environment local or prod.
makeNewOutboundEmailNewOutboundEmailA test message, an idempotency key prefixed test:, no sender account.
makeEmailSuppressionEmailSuppressionA lower-cased faker address, reason bounced or complained, an email id.
makeOutboundEmailOutboundEmailThe same plus an id, status: "queued", attempts: 0, externalId, lastError and sentAt null, updatedAt equal to createdAt.

Events

FactoryReturnsNotable defaults
makeIngestedEventIngestedEventPort-shape event, what a verifier returns before persistence. Neutral provider: "test-provider" and eventType: "test.event" so a domain test never encodes a provider; externalId prefixed evt_, payload: { foo: "bar" }.
makePersistedIngestedEventPersistedIngestedEventEntity-shape event after repo.create: same defaults plus id, status: "pending", attempts: 0, lastError and processedAt null, updatedAt equal to createdAt.