import { describe, it, expect } from 'vitest' import { mergeLineIntoMarkerBlock, replaceMarkerBlock, spliceDiMarkerBlock } from '../di-markers.js' const BEGIN = '// <<< BUSINESS-SERVICES-DI BEGIN >>>' const END = '// <<< BUSINESS-SERVICES-DI END >>>' const LINE_A = 'services.AddScoped();' const LINE_B = 'services.AddScoped();' const HOST_WITH_MARKERS = `public static IServiceCollection AddInfrastructure(this IServiceCollection services) { // <<< BUSINESS-SERVICES-DI BEGIN >>> // services.AddScoped(); // <<< BUSINESS-SERVICES-DI END >>> return services; } ` const HOST_WITHOUT_MARKERS = `public static IServiceCollection AddInfrastructure(this IServiceCollection services) { services.AddScoped(); return services; } ` describe('lib/di-markers — replaceMarkerBlock / spliceDiMarkerBlock (extraction contract)', () => { it('replaces an existing marker block content idempotently', () => { const block = ` ${BEGIN}\n ${LINE_A}\n ${END}` const once = spliceDiMarkerBlock(HOST_WITH_MARKERS, BEGIN, END, block)! expect(once).toContain(LINE_A) // Re-splicing the identical block reports "already up to date". expect(spliceDiMarkerBlock(once, BEGIN, END, block)).toBeNull() }) it('falls back to inserting before the last `return services;` when markers are absent', () => { const block = ` ${BEGIN}\n ${LINE_A}\n ${END}` const next = spliceDiMarkerBlock(HOST_WITHOUT_MARKERS, BEGIN, END, block)! expect(next.indexOf(LINE_A)).toBeGreaterThan(next.indexOf('ISomethingElse')) expect(next.indexOf(LINE_A)).toBeLessThan(next.indexOf('return services;')) }) it('replaceMarkerBlock returns undefined when the markers are missing (caller fallback)', () => { expect(replaceMarkerBlock(HOST_WITHOUT_MARKERS, BEGIN, END, 'x')).toBeUndefined() }) }) describe('lib/di-markers — mergeLineIntoMarkerBlock', () => { it('appends a line into the block, replacing the commented example', () => { const r = mergeLineIntoMarkerBlock(HOST_WITH_MARKERS, BEGIN, END, LINE_A) expect(r.status).toBe('patched') const src = (r as { source: string }).source expect(src).toContain(LINE_A) // The template's commented example is replaced, not kept alongside. expect(src).not.toContain('// services.AddScoped();') }) it('unions lines — a second entity lands under the first, first order preserved', () => { const first = mergeLineIntoMarkerBlock(HOST_WITH_MARKERS, BEGIN, END, LINE_A) const second = mergeLineIntoMarkerBlock((first as { source: string }).source, BEGIN, END, LINE_B) const src = (second as { source: string }).source expect(src.indexOf(LINE_A)).toBeLessThan(src.indexOf(LINE_B)) // One block only — the markers never duplicate. expect(src.split(BEGIN).length - 1).toBe(1) }) it('re-merging the same line is a no-op (idempotent re-run)', () => { const first = mergeLineIntoMarkerBlock(HOST_WITH_MARKERS, BEGIN, END, LINE_A) const again = mergeLineIntoMarkerBlock((first as { source: string }).source, BEGIN, END, LINE_A) expect(again).toEqual({ status: 'unchanged', reason: 'line-in-block' }) }) it('a line the project already registered OUTSIDE the block is left alone (no double-register)', () => { const hand = HOST_WITH_MARKERS.replace( 'return services;', `${LINE_A}\n return services;`, ) const r = mergeLineIntoMarkerBlock(hand, BEGIN, END, LINE_A) expect(r).toEqual({ status: 'unchanged', reason: 'line-outside-block' }) }) it('markers absent → creates the block through the return-services fallback (upgrade path)', () => { const r = mergeLineIntoMarkerBlock(HOST_WITHOUT_MARKERS, BEGIN, END, LINE_A) expect(r.status).toBe('patched') const src = (r as { source: string }).source expect(src).toContain(BEGIN) expect(src.indexOf(LINE_A)).toBeLessThan(src.indexOf('return services;')) }) it('no insertion point at all → failed, never a silent drop', () => { const r = mergeLineIntoMarkerBlock('', BEGIN, END, LINE_A) expect(r).toEqual({ status: 'failed', reason: 'no-insertion-point' }) }) })