diff --git a/packages/downstream-contract/src/additional-domains.fixtures.ts b/packages/downstream-contract/src/additional-domains.fixtures.ts new file mode 100644 index 0000000000..c61d9993c8 --- /dev/null +++ b/packages/downstream-contract/src/additional-domains.fixtures.ts @@ -0,0 +1,151 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +// FROZEN bare-literal fixtures for the remaining authoring domains (#2035), so +// the downstream contract exercises the FULL writable surface, not just a few +// domains. Authored the way a third party on a published release did, typed with +// the spec's own input aliases. DO NOT migrate these to the defineX factories +// and DO NOT edit them to make a failing spec change pass — see the README. +import type { DatasourceInput, MappingInput, CubeInput, ObjectExtensionInput } from '@objectstack/spec/data'; +import type { ConnectorInput } from '@objectstack/spec/integration'; +import type { PolicyInput, SharingRuleInput, PermissionSetInput } from '@objectstack/spec/security'; +import type { RoleInput } from '@objectstack/spec/identity'; +import type { EmailTemplateDefinitionInput, TranslationBundleInput } from '@objectstack/spec/system'; +import type { WebhookInput } from '@objectstack/spec/automation'; +import type { ThemeInput } from '@objectstack/spec/ui'; + +export const DcDatasource: DatasourceInput = { + name: 'dc_primary', + label: 'DC Primary', + driver: 'sqlite', + config: { filename: ':memory:' }, + active: true, +}; + +export const DcConnector: ConnectorInput = { + name: 'dc_hubspot', + label: 'DC HubSpot', + type: 'saas', + description: 'Example SaaS connector.', + authentication: { + type: 'oauth2', + authorizationUrl: 'https://example.com/oauth/authorize', + tokenUrl: 'https://example.com/oauth/token', + clientId: 'env:DC_CLIENT_ID', + clientSecret: 'env:DC_CLIENT_SECRET', + }, + actions: [ + { + key: 'create_contact', + label: 'Create Contact', + description: 'Create a contact', + inputSchema: { type: 'object', properties: { email: { type: 'string' } }, required: ['email'] }, + }, + ], +}; + +export const DcPolicy: PolicyInput = { + name: 'dc_default_policy', + isDefault: true, + password: { minLength: 12, requireUppercase: true, requireNumbers: true }, + session: { idleTimeout: 30, absoluteTimeout: 480 }, +}; + +export const DcSharingRule: SharingRuleInput = { + type: 'criteria', + name: 'dc_share_customers', + label: 'Customers → managers', + object: 'dc_account', + condition: 'record.stage == "customer"', + accessLevel: 'read', + sharedWith: { type: 'role', value: 'dc_manager' }, + active: true, +}; + +export const DcRole: RoleInput = { + name: 'dc_manager', + label: 'DC Manager', + description: 'Manager role.', +}; + +export const DcPermissionSet: PermissionSetInput = { + name: 'dc_user', + label: 'DC User', + isProfile: false, + objects: { + dc_account: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false }, + }, +}; + +export const DcEmail: EmailTemplateDefinitionInput = { + name: 'dc.welcome', + label: 'DC Welcome', + category: 'marketing', + locale: 'en-US', + subject: 'Welcome {{contact.first_name}}', + bodyHtml: '
Hi {{contact.first_name}}, welcome.
', + bodyText: 'Hi {{contact.first_name}}, welcome.', + variables: [{ name: 'contact.first_name', type: 'string', required: true }], + active: true, +}; + +export const DcWebhook: WebhookInput = { + name: 'dc_account_changed', + label: 'Account Changed', + object: 'dc_account', + triggers: ['create', 'update'], + url: 'https://hooks.example.com/dc/account', + method: 'POST', + isActive: true, +}; + +export const DcObjectExtension: ObjectExtensionInput = { + extend: 'dc_account', + label: 'Account (extended)', + fields: { + note: { name: 'note', label: 'Note', type: 'text', maxLength: 255 }, + }, + priority: 210, +}; + +export const DcCube: CubeInput = { + name: 'dc_pipeline', + title: 'DC Pipeline', + description: 'Account analytics.', + sql: 'dc_account', + measures: { + count: { name: 'count', label: 'Count', type: 'count', sql: '*' }, + }, + dimensions: { + stage: { name: 'stage', label: 'Stage', type: 'string', sql: 'stage' }, + }, +}; + +export const DcMapping: MappingInput = { + name: 'dc_csv_import', + label: 'CSV Import: Accounts', + sourceFormat: 'csv', + targetObject: 'dc_account', + mode: 'upsert', + upsertKey: ['name'], + fieldMapping: [{ source: 'Name', target: 'name', transform: 'none' }], +}; + +export const DcTheme: ThemeInput = { + name: 'dc_light', + label: 'DC Light', + mode: 'light', + colors: { + primary: '#1E6FD9', + secondary: '#6C757D', + background: '#FFFFFF', + surface: '#F8F9FA', + text: '#212529', + }, +}; + +export const DcTranslationBundle: TranslationBundleInput = { + en: { + objects: { dc_account: { label: 'Account', pluralLabel: 'Accounts' } }, + messages: { 'common.save': 'Save' }, + }, +}; diff --git a/packages/downstream-contract/test/contract.test.ts b/packages/downstream-contract/test/contract.test.ts index cde669ad18..364353d053 100644 --- a/packages/downstream-contract/test/contract.test.ts +++ b/packages/downstream-contract/test/contract.test.ts @@ -1,11 +1,18 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect } from 'vitest'; -import { ActionSchema, ReportSchema, PageSchema } from '@objectstack/spec/ui'; +import { ActionSchema, ReportSchema, PageSchema, ThemeSchema } from '@objectstack/spec/ui'; +import { DatasourceSchema, MappingSchema, CubeSchema, ObjectExtensionSchema } from '@objectstack/spec/data'; +import { ConnectorSchema } from '@objectstack/spec/integration'; +import { PolicySchema, SharingRuleSchema, PermissionSetSchema } from '@objectstack/spec/security'; +import { RoleSchema } from '@objectstack/spec/identity'; +import { EmailTemplateDefinitionSchema, TranslationBundleSchema } from '@objectstack/spec/system'; +import { WebhookSchema } from '@objectstack/spec/automation'; import { LogCallAction } from '../src/log-call.action.js'; import { AccountsByStageReport } from '../src/pipeline.report.js'; import { WelcomePage } from '../src/welcome.page.js'; import { ContractStack } from '../src/stack.js'; +import * as more from '../src/additional-domains.fixtures.js'; // FROZEN — see README. A failure means a spec change dropped or narrowed a // property a published-spec third party already uses. That is breaking: adjust @@ -22,4 +29,26 @@ describe('downstream consumer contract (#2035)', () => { expect(ContractStack.objects).toHaveLength(1); expect(ContractStack.actions).toHaveLength(2); }); + + // Full writable-surface coverage: one frozen bare-literal per remaining domain + // (#2035). A narrowed/removed schema property on ANY of these breaks here. + const cases: Array<[string, { parse: (v: unknown) => unknown }, unknown]> = [ + ['Datasource', DatasourceSchema, more.DcDatasource], + ['Connector', ConnectorSchema, more.DcConnector], + ['Policy', PolicySchema, more.DcPolicy], + ['SharingRule', SharingRuleSchema, more.DcSharingRule], + ['Role', RoleSchema, more.DcRole], + ['PermissionSet', PermissionSetSchema, more.DcPermissionSet], + ['EmailTemplateDefinition', EmailTemplateDefinitionSchema, more.DcEmail], + ['Webhook', WebhookSchema, more.DcWebhook], + ['ObjectExtension', ObjectExtensionSchema, more.DcObjectExtension], + ['Cube', CubeSchema, more.DcCube], + ['Mapping', MappingSchema, more.DcMapping], + ['Theme', ThemeSchema, more.DcTheme], + ['TranslationBundle', TranslationBundleSchema, more.DcTranslationBundle], + ]; + + it.each(cases)('%s bare-literal parses against the current spec', (_name, schema, fixture) => { + expect(() => schema.parse(fixture)).not.toThrow(); + }); });