// Tests der Audio-CD-Rechnungen und der MusicBrainz-Kennung — die // Referenzzahlen sind die ECHTE AC/DC-„Back in Black"-Antwort vom // 29.08.2026 (test_musicbrainz_cd.py: „abgefragt, nicht erinnert"). import { describe, expect, it } from 'vitest' import { bloecke, dateiname, dauerText, flacBefehl, msfZuLba, rawReadInfo, sauberName, tocZerlegen, wavKopf, OFFSET_EINHEIT, SEKTOR_BYTES, VORLAUF_FRAMES, } from '../src/kern/rip/audiocd' import { discId, kennungAusSpuren, tagsJeSpur, PLAETZE } from '../src/kern/metadaten/musicbrainz' import { extractGueltigBis, extractKey } from '../src/kern/werkzeuge/schluessel' const ACDC_VERSAETZE = [182, 23637, 47507, 63692, 79615, 98742, 117937, 133712, 151660, 170112] const ACDC_LEAD_OUT = 189281 const ACDC_KENNUNG = '3KVSIWn_ewv9z0cCizmOJzDWtsQ-' describe('MusicBrainz-DiscID', () => { it('DIE Referenz: stimmt mit MusicBrainz überein', () => { expect(discId(ACDC_VERSAETZE, ACDC_LEAD_OUT, 1, 10)).toBe(ACDC_KENNUNG) }) it('der Vorlauf wird wieder HINZUgerechnet (LBA→MSF-Falle)', () => { const spuren = ACDC_VERSAETZE.map((versatz, i) => ({ nr: i + 1, start: versatz - VORLAUF_FRAMES, sektoren: (i + 1 < ACDC_VERSAETZE.length ? ACDC_VERSAETZE[i + 1] : ACDC_LEAD_OUT) - versatz, audio: true, dauer: '', })) expect(kennungAusSpuren(spuren)).toBe(ACDC_KENNUNG) }) it('MusicBrainz-Alphabet: kein + / =', () => { const kennung = discId(ACDC_VERSAETZE, ACDC_LEAD_OUT, 1, 10) expect(/[+/=]/.test(kennung)).toBe(false) }) it('immer 99 Plätze — und jede Zahl zählt', () => { expect(PLAETZE).toBe(99) expect(discId([150, 20000], 40000, 1, 2)).not.toBe(discId([150, 20001], 40000, 1, 2)) expect(discId([], 1000)).toBe('') expect(kennungAusSpuren([])).toBe('') }) }) describe('MSF/LBA und TOC', () => { it('msfZuLba zieht die 150 Vorlauf-Frames ab', () => { expect(msfZuLba(0, 2, 0)).toBe(0) expect(msfZuLba(0, 2, 32)).toBe(32) expect(msfZuLba(1, 0, 0)).toBe(75 * 60 - 150) expect(dauerText(4500)).toBe('1:00') expect(dauerText(75 * 61)).toBe('1:01') }) it('tocZerlegen liest Spuren, erkennt Daten-Tracks, braucht den Lead-Out', () => { // CDROM_TOC: 4 Byte Kopf + Einträge à 8 Byte (…, Adr/Control, Nr, …, 0,M,S,F) const toc = Buffer.alloc(4 + 3 * 8) const eintrag = (stelle: number, control: number, nr: number, m: number, s: number, f: number): void => { toc[4 + stelle * 8 + 1] = control toc[4 + stelle * 8 + 2] = nr toc[4 + stelle * 8 + 5] = m toc[4 + stelle * 8 + 6] = s toc[4 + stelle * 8 + 7] = f } eintrag(0, 0x00, 1, 0, 2, 0) // Audio ab LBA 0 eintrag(1, 0x04, 2, 1, 2, 0) // DATEN-Track ab LBA 4500 eintrag(2, 0x00, 0xaa, 2, 2, 0) // Lead-Out bei LBA 9000 const spuren = tocZerlegen(toc) expect(spuren).toHaveLength(2) expect(spuren[0]).toMatchObject({ nr: 1, start: 0, sektoren: 4500, audio: true, dauer: '1:00' }) expect(spuren[1]).toMatchObject({ nr: 2, audio: false }) }) it('ohne Endmarke fehlt der letzten Spur die Länge — sie fällt weg', () => { const toc = Buffer.alloc(4 + 8) toc[4 + 2] = 1 toc[4 + 6] = 2 expect(tocZerlegen(toc)).toEqual([]) expect(tocZerlegen(Buffer.alloc(4))).toEqual([]) }) }) describe('Lesen und Kodieren', () => { it('bloecke bleibt unter der 64-KB-Treibergrenze', () => { const teile = bloecke(1000, 60) expect(teile[0]).toEqual([1000, 27]) expect(teile[1]).toEqual([1027, 27]) expect(teile[2]).toEqual([1000 + 54, 6]) expect(27 * SEKTOR_BYTES).toBeLessThan(64 * 1024) }) it('rawReadInfo: DiskOffset in 2048ern, obwohl der Sektor 2352 hat', () => { const info = rawReadInfo(100, 27) expect(info.readBigInt64LE(0)).toBe(100n * BigInt(OFFSET_EINHEIT)) expect(info.readUInt32LE(8)).toBe(27) expect(info.readUInt32LE(12)).toBe(2) // CDDA }) it('wavKopf: 44 Byte, RIFF-Größenfelder stimmen', () => { const kopf = wavKopf(1000) expect(kopf.length).toBe(44) expect(kopf.toString('ascii', 0, 4)).toBe('RIFF') expect(kopf.readUInt32LE(4)).toBe(1036) expect(kopf.readUInt32LE(40)).toBe(1000) expect(kopf.readUInt32LE(24)).toBe(44100) }) it('sauberName: AC/DC macht keinen Unterordner auf', () => { expect(sauberName('AC/DC - Back in Black')).toBe('ACDC - Back in Black') expect(sauberName('Ende. ')).toBe('Ende') }) it('dateiname mit und ohne Titel', () => { expect(dateiname(1, 'Hells Bells')).toBe('01 - Hells Bells') expect(dateiname(2)).toBe('Track 02') }) it('flacBefehl trägt Tags einzeln', () => { const befehl = flacBefehl('a.wav', 'b.flac', { TITLE: 'X', LEER: '' }) expect(befehl).toContain('--tag') expect(befehl[befehl.indexOf('--tag') + 1]).toBe('TITLE=X') expect(befehl.join(' ')).not.toContain('LEER=') expect(befehl.at(-1)).toBe('a.wav') }) }) describe('tagsJeSpur', () => { it('setzt Künstler mit Bindewörtern zusammen, nimmt nur das Jahr', () => { const tags = tagsJeSpur({ releases: [ { title: 'Back in Black', date: '1980-07-25', 'artist-credit': [{ name: 'AC/DC' }], media: [ { tracks: [ { position: 1, title: 'Hells Bells', 'artist-credit': [{ name: 'Simon', joinphrase: ' & ' }, { name: 'Garfunkel' }] }, { position: '2', title: 'Shoot to Thrill' }, ], }, ], }, ], }) expect(tags.get(1)?.['ARTIST']).toBe('Simon & Garfunkel') expect(tags.get(2)?.['ARTIST']).toBe('AC/DC') expect(tags.get(1)?.['DATE']).toBe('1980') expect(tags.get(2)?.['TRACKNUMBER']).toBe('2') expect(tagsJeSpur(null).size).toBe(0) }) }) describe('Schlüsselkette (§ 9.1)', () => { it('extractKey findet den Beta-Key im Forum-HTML', () => { const html = 'T-' + 'x'.repeat(60) + '' expect(extractKey(html)).toBe('T-' + 'x'.repeat(60)) expect(extractKey('kein Key hier T-kurz')).toBe('') }) it('extractGueltigBis liest das Ablaufdatum (best effort)', () => { expect(extractGueltigBis('The key is valid until September 30th, 2026 or so')).toBe('September 30, 2026') expect(extractGueltigBis('nichts')).toBe('') }) // 5.7.0 (Durchsicht 12.09.2026, F1): So steht es WIRKLICH auf der Forum- // Seite — der September-Key trägt ein @, und das Datum heißt „end of". // Das alte Muster endete am @ und lieferte 62 von 68 Zeichen. it('der echte September-2026-Key mit @ kommt ganz an', () => { const key = 'T-vwvnrmF87JuRPLYYO8HnIrZR_LCAF7JXs9iJEm10moGpjEgkvmPTsf6Ox4BI@N5rPp' const html = `
The current beta key is:
${key}
The key is valid until end of September 2026. Please check back for updated key on this page.
` expect(extractKey(html)).toBe(key) expect(extractKey(html)).toHaveLength(68) expect(extractGueltigBis(html)).toBe('end of September 2026') }) it('der Key endet am HTML-Zeichen, nicht mitten im Wort', () => { const key = 'T-' + 'a'.repeat(66) expect(extractKey(`${key}

valid

`)).toBe(key) expect(extractKey(`key: ${key} (valid until end of October 2026)`)).toBe(key) }) })