import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ApplicationType, effectiveCommitTemplateName, getApplicationType, isSdkApiCommitTemplate, isSdkApiTemplate, LEGACY_COMMIT_TEMPLATE_NAME } from './index'; describe('SDK API application templates', () => { it('classifies app-fullstack as the 3.0 SDK API template', () => { expect(isSdkApiTemplate('app-fullstack')).toBe(true); expect(isSdkApiCommitTemplate('app-fullstack')).toBe(true); }); it.each([undefined, null, '', LEGACY_COMMIT_TEMPLATE_NAME, 'shadcn-demo-app'])( 'classifies %s as a legacy non-SDK API commit template', (templateName) => { expect(isSdkApiCommitTemplate(templateName)).toBe(false); } ); it('normalizes missing commit templates to the legacy template name', () => { expect(effectiveCommitTemplateName(undefined)).toBe(LEGACY_COMMIT_TEMPLATE_NAME); expect(effectiveCommitTemplateName(null)).toBe(LEGACY_COMMIT_TEMPLATE_NAME); }); }); describe('getApplicationType', () => { it('classifies an application with devEnvEnabled=false as 1.0', () => { expect(getApplicationType({ devEnvEnabled: false, templateName: null })).toBe(ApplicationType.V1); }); it('classifies a legacy 1.0 application as 1.0 even when a template name is present', () => { // devEnvEnabled is the 1.0 vs 2.0+ discriminator; template name alone never promotes a 1.0 app. expect(getApplicationType({ devEnvEnabled: false, templateName: 'app-fullstack' })).toBe(ApplicationType.V1); }); it.each([null, 'shadcn-demo-app', 'blank-app'])('classifies a code-mode application on template %s as 2.0', (templateName) => { expect(getApplicationType({ devEnvEnabled: true, templateName })).toBe(ApplicationType.V2); }); it('classifies a code-mode application on an SDK API template as 3.0', () => { expect(getApplicationType({ devEnvEnabled: true, templateName: 'app-fullstack' })).toBe(ApplicationType.V3); }); it.each([undefined, null])('returns undefined when devEnvEnabled was not projected (%s) by the query', (devEnvEnabled) => { // dev_env_enabled is NOT NULL DEFAULT false, so a nullish value means unselected. expect(getApplicationType({ devEnvEnabled, templateName: 'app-fullstack' })).toBeUndefined(); }); it('returns undefined when templateName was not projected for a code-mode app', () => { // Guessing 2.0 here would silently misclassify every 3.0 app behind a partial select. expect(getApplicationType({ devEnvEnabled: true })).toBeUndefined(); }); it('does not need templateName to classify a legacy app', () => { expect(getApplicationType({ devEnvEnabled: false })).toBe(ApplicationType.V1); }); it('treats a genuinely null template as a real value, not an unprojected one', () => { expect(getApplicationType({ devEnvEnabled: true, templateName: null })).toBe(ApplicationType.V2); }); it('does not classify from a commit-scoped template resolution', () => { // A commit predating a 2.0 -> 3.0 migration resolves to the legacy template. // Classifying off that instead of the app row is what would misreport 3.0 as 2.0. const migratedAppRow = { devEnvEnabled: true, templateName: 'app-fullstack' }; const preMigrationCommitTemplate = effectiveCommitTemplateName(null); expect(getApplicationType(migratedAppRow)).toBe(ApplicationType.V3); expect(getApplicationType({ devEnvEnabled: true, templateName: preMigrationCommitTemplate })).toBe(ApplicationType.V2); }); it('exposes the version strings the analytics pipeline filters on', () => { expect(ApplicationType.V1).toBe('1.0'); expect(ApplicationType.V2).toBe('2.0'); expect(ApplicationType.V3).toBe('3.0'); }); }); describe('SDK_API_TEMPLATES stays in sync with the demo-apps template config', () => { // isSdkApiTemplate drives the 3.0-vs-2.0 half of getApplicationType, which is a // billing input. A new SDK API template shipped without updating the allowlist // would not fail safe -- it would confidently classify those apps as 2.0. The // template config is the source of truth, so pin the two together here rather // than relying on the "must stay in sync" comment alone. // This package compiles to CommonJS, so import.meta is unavailable; vitest runs // with the package directory as cwd. const configPath = resolve(process.cwd(), '../demo-apps/config.json'); const templates = (JSON.parse(readFileSync(configPath, 'utf8')) as { templates: Record }).templates; it('has at least one template to check', () => { expect(Object.keys(templates).length).toBeGreaterThan(0); }); it.each(Object.entries(templates))('agrees with config.json on whether %s is an SDK API template', (name, template) => { expect(isSdkApiTemplate(name)).toBe(template.sdkApi === true); }); });