support-tickets.test.ts99 lines · main
| 1 | /** |
| 2 | * Pure-unit tests for the support-ticket helpers that don't need a DB: |
| 3 | * - the routing-tag → topic-code parser |
| 4 | * - the ticket-number FORMAT (counter mocked / passed in) |
| 5 | * |
| 6 | * The DB-dependent paths — generateTicketNumber's atomic counter, the |
| 7 | * create-path transaction, and every endpoint — need a live local DB and |
| 8 | * are NOT covered here (integration-tested separately). |
| 9 | */ |
| 10 | |
| 11 | import { describe, expect, test } from 'bun:test'; |
| 12 | |
| 13 | import { |
| 14 | formatTicketNumber, |
| 15 | parseRoutingTags, |
| 16 | primaryTopicCode, |
| 17 | renderTicketNumber, |
| 18 | ticketDayKey, |
| 19 | } from './support-tickets.js'; |
| 20 | |
| 21 | describe('parseRoutingTags', () => { |
| 22 | test('extracts routing tags in subject order', () => { |
| 23 | expect(parseRoutingTags('#support #billing #technical #self-hosting')).toEqual([ |
| 24 | 'support', |
| 25 | 'billing', |
| 26 | 'technical', |
| 27 | 'self-hosting', |
| 28 | ]); |
| 29 | }); |
| 30 | |
| 31 | test('ignores non-routing chips and de-dupes', () => { |
| 32 | expect(parseRoutingTags('#sales #support #support #urgent')).toEqual(['support']); |
| 33 | }); |
| 34 | |
| 35 | test('returns empty for no/blank/untagged subject', () => { |
| 36 | expect(parseRoutingTags(null)).toEqual([]); |
| 37 | expect(parseRoutingTags(undefined)).toEqual([]); |
| 38 | expect(parseRoutingTags('')).toEqual([]); |
| 39 | expect(parseRoutingTags('just a plain subject line')).toEqual([]); |
| 40 | }); |
| 41 | |
| 42 | test('is case-insensitive on the tag text', () => { |
| 43 | expect(parseRoutingTags('#Support #BILLING')).toEqual(['support', 'billing']); |
| 44 | }); |
| 45 | }); |
| 46 | |
| 47 | describe('primaryTopicCode', () => { |
| 48 | test('maps each routing tag to its code', () => { |
| 49 | expect(primaryTopicCode('#support')).toBe('SUP'); |
| 50 | expect(primaryTopicCode('#billing')).toBe('BIL'); |
| 51 | expect(primaryTopicCode('#technical')).toBe('TEC'); |
| 52 | expect(primaryTopicCode('#self-hosting')).toBe('SLF'); |
| 53 | }); |
| 54 | |
| 55 | test('uses the FIRST routing tag in the subject', () => { |
| 56 | expect(primaryTopicCode('#billing #support')).toBe('BIL'); |
| 57 | expect(primaryTopicCode('#technical #billing #support')).toBe('TEC'); |
| 58 | }); |
| 59 | |
| 60 | test('returns null when no routing tag is present', () => { |
| 61 | expect(primaryTopicCode('#sales #partnerships')).toBeNull(); |
| 62 | expect(primaryTopicCode('hello there')).toBeNull(); |
| 63 | expect(primaryTopicCode(null)).toBeNull(); |
| 64 | }); |
| 65 | }); |
| 66 | |
| 67 | describe('formatTicketNumber + renderTicketNumber', () => { |
| 68 | const day = new Date('2026-06-29T12:00:00Z'); |
| 69 | |
| 70 | test('formats <CODE><YYMMDD>-<6-digit counter> (no # in storage)', () => { |
| 71 | expect(formatTicketNumber('SUP', day, 1)).toBe('SUP260629-000001'); |
| 72 | }); |
| 73 | |
| 74 | test('renders the stored value with a leading # for API responses', () => { |
| 75 | expect(renderTicketNumber(formatTicketNumber('SUP', day, 1))).toBe('#SUP260629-000001'); |
| 76 | }); |
| 77 | |
| 78 | test('zero-pads the counter to 6 digits and grows past it', () => { |
| 79 | expect(formatTicketNumber('BIL', day, 42)).toBe('BIL260629-000042'); |
| 80 | expect(formatTicketNumber('TEC', day, 123456)).toBe('TEC260629-123456'); |
| 81 | }); |
| 82 | |
| 83 | test('uses UTC for the YYMMDD stamp', () => { |
| 84 | // 23:30 UTC on the 29th stays the 29th regardless of host timezone. |
| 85 | const lateUtc = new Date('2026-06-29T23:30:00Z'); |
| 86 | expect(formatTicketNumber('SLF', lateUtc, 7)).toBe('SLF260629-000007'); |
| 87 | }); |
| 88 | |
| 89 | test('renderTicketNumber passes through null', () => { |
| 90 | expect(renderTicketNumber(null)).toBeNull(); |
| 91 | }); |
| 92 | }); |
| 93 | |
| 94 | describe('ticketDayKey', () => { |
| 95 | test('is the UTC YYYY-MM-DD the counter resets on', () => { |
| 96 | expect(ticketDayKey(new Date('2026-06-29T12:00:00Z'))).toBe('2026-06-29'); |
| 97 | expect(ticketDayKey(new Date('2026-01-05T00:00:00Z'))).toBe('2026-01-05'); |
| 98 | }); |
| 99 | }); |