@@ -29,13 +29,33 @@ import (
2929// Test helpers
3030// ---------------------------------------------------------------------------
3131
32- // newTestClient builds an AllAnimeClient pointed at the given httptest server.
32+ // allAnimeKey / allAnimeKeyHex are a FIXED fixture key for the offline crypto
33+ // round-trip tests. Production derives the real key dynamically per epoch (see
34+ // fetchAAKeys); these only need to be a valid 32-byte AES-256 key shared by the
35+ // encrypt and decrypt sides of the tests.
36+ const allAnimeKeyHex = "22196fa6afca95309fdabe9a3534b87cd2454e50efeabfcbdbdfd3de678b3982"
37+
38+ var allAnimeKey = func () []byte {
39+ k , err := hex .DecodeString (allAnimeKeyHex )
40+ if err != nil || len (k ) != 32 {
41+ panic ("allanime test: bad fixture key" )
42+ }
43+ return k
44+ }()
45+
46+ // testAAEpoch is the fixture epoch the injected key is paired with.
47+ const testAAEpoch = "4128"
48+
49+ // newTestClient builds an AllAnimeClient pointed at the given httptest server,
50+ // with the fixture key injected so getAAKeys never scrapes the live bundle.
3351func newTestClient (serverURL string ) * AllAnimeClient {
3452 return & AllAnimeClient {
3553 client : util .GetFastClient (),
3654 referer : AllAnimeReferer ,
3755 apiBase : serverURL ,
3856 userAgent : netx .UserAgent ,
57+ keys : & aaKeys {key : allAnimeKey , epoch : testAAEpoch },
58+ keysExp : time .Now ().Add (time .Hour ),
3959 }
4060}
4161
@@ -262,8 +282,8 @@ func TestAllAnimeGetLinksClassifiesHTMLBodyAsSourceUnavailable(t *testing.T) {
262282
263283func TestAllAnimeKeyMatchesOpenSSL (t * testing.T ) {
264284 t .Parallel ()
265- // Literal 32-byte hex key, rotated 2026-07-08 (ani-cli PR #1772). It is no
266- // longer derived from a passphrase; it must equal the constant verbatim .
285+ // The production key is now derived per epoch (fetchAAKeys); this pins the
286+ // offline test fixture key as a valid 32-byte AES-256 key .
267287 assert .Equal (t , allAnimeKeyHex , hex .EncodeToString (allAnimeKey ))
268288}
269289
@@ -393,7 +413,7 @@ func TestDecodeToBeParsedRoundTrip(t *testing.T) {
393413 plaintext := `{"data":{"episode":{"sourceUrls":[{"sourceUrl":"--504c4c484b021717","sourceName":"TestProvider"}]}}}`
394414 blob := encryptToBeParsed (t , plaintext )
395415
396- sources , err := decodeToBeParsed (blob )
416+ sources , err := decodeToBeParsed (blob , allAnimeKey )
397417 require .NoError (t , err )
398418 require .Len (t , sources , 1 )
399419 assert .Equal (t , "TestProvider" , sources [0 ].sourceName )
@@ -409,7 +429,7 @@ func TestDecodeToBeParsedMultipleSources(t *testing.T) {
409429 ]}}}`
410430 blob := encryptToBeParsed (t , plaintext )
411431
412- sources , err := decodeToBeParsed (blob )
432+ sources , err := decodeToBeParsed (blob , allAnimeKey )
413433 require .NoError (t , err )
414434 require .Len (t , sources , 3 )
415435 assert .Equal (t , "Provider1" , sources [0 ].sourceName )
@@ -421,14 +441,14 @@ func TestDecodeToBeParsedMultipleSources(t *testing.T) {
421441func TestDecodeToBeParsedTooShort (t * testing.T ) {
422442 t .Parallel ()
423443 blob := base64 .StdEncoding .EncodeToString ([]byte ("short" ))
424- _ , err := decodeToBeParsed (blob )
444+ _ , err := decodeToBeParsed (blob , allAnimeKey )
425445 require .Error (t , err )
426446 assert .Contains (t , err .Error (), "too short" )
427447}
428448
429449func TestDecodeToBeParsedBadBase64 (t * testing.T ) {
430450 t .Parallel ()
431- _ , err := decodeToBeParsed ("!!!not-base64!!!" )
451+ _ , err := decodeToBeParsed ("!!!not-base64!!!" , allAnimeKey )
432452 require .Error (t , err )
433453 assert .Contains (t , err .Error (), "base64" )
434454}
@@ -437,7 +457,7 @@ func TestDecodeToBeParsedExactly12BytesNoCiphertext(t *testing.T) {
437457 t .Parallel ()
438458 // 12 bytes < 29 (GCM minimum: 1 version + 12 nonce + 16 tag) → too short
439459 blob := base64 .StdEncoding .EncodeToString (make ([]byte , 12 ))
440- _ , err := decodeToBeParsed (blob )
460+ _ , err := decodeToBeParsed (blob , allAnimeKey )
441461 require .Error (t , err )
442462 assert .Contains (t , err .Error (), "too short" )
443463}
@@ -446,7 +466,7 @@ func TestDecodeToBeParsedExactly13BytesMinimal(t *testing.T) {
446466 t .Parallel ()
447467 // 13 bytes < 29 → too short
448468 blob := base64 .StdEncoding .EncodeToString (make ([]byte , 13 ))
449- _ , err := decodeToBeParsed (blob )
469+ _ , err := decodeToBeParsed (blob , allAnimeKey )
450470 assert .Error (t , err )
451471 assert .Contains (t , err .Error (), "too short" )
452472}
@@ -456,7 +476,7 @@ func TestDecodeToBeParsedExactly28BytesTooShort(t *testing.T) {
456476 // GCM minimum is 29 bytes (1 version + 12 nonce + 16 tag + 0 plaintext).
457477 // 28 bytes trips the length guard before any crypto runs.
458478 blob := base64 .StdEncoding .EncodeToString (make ([]byte , 28 ))
459- _ , err := decodeToBeParsed (blob )
479+ _ , err := decodeToBeParsed (blob , allAnimeKey )
460480 require .Error (t , err )
461481 assert .Contains (t , err .Error (), "too short" )
462482}
@@ -466,7 +486,7 @@ func TestDecodeToBeParsedAllZeroBytesFailsAuth(t *testing.T) {
466486 // A 29-byte all-zero blob passes the length guard but is not a valid GCM
467487 // message (the zero tag won't authenticate), so Open rejects it.
468488 blob := base64 .StdEncoding .EncodeToString (make ([]byte , 29 ))
469- _ , err := decodeToBeParsed (blob )
489+ _ , err := decodeToBeParsed (blob , allAnimeKey )
470490 require .Error (t , err )
471491 assert .Contains (t , err .Error (), "GCM decrypt failed" )
472492}
@@ -487,7 +507,7 @@ func TestDecodeToBeParsedCorruptedCiphertext(t *testing.T) {
487507 }
488508 corruptBlob := base64 .StdEncoding .EncodeToString (raw )
489509
490- _ , err = decodeToBeParsed (corruptBlob )
510+ _ , err = decodeToBeParsed (corruptBlob , allAnimeKey )
491511 require .Error (t , err , "tampered ciphertext must not produce a usable result" )
492512 assert .Contains (t , err .Error (), "GCM decrypt failed" ,
493513 "GCM authenticates — tampering must be rejected at the cipher layer" )
@@ -503,7 +523,7 @@ func TestDecodeToBeParsedTruncatedCiphertext(t *testing.T) {
503523 // 16 bytes < 29 minimum → "too short" before GCM is even attempted
504524 truncated := base64 .StdEncoding .EncodeToString (raw [:16 ])
505525
506- _ , err = decodeToBeParsed (truncated )
526+ _ , err = decodeToBeParsed (truncated , allAnimeKey )
507527 assert .Error (t , err , "truncated blob should fail" )
508528}
509529
@@ -513,7 +533,7 @@ func TestDecodeToBeParsedRegexFallbackSourceUrlBeforeSourceName(t *testing.T) {
513533 plaintext := `[{"sourceUrl":"--5959","sourceName":"Fallback1"}]`
514534 blob := encryptToBeParsed (t , plaintext )
515535
516- sources , err := decodeToBeParsed (blob )
536+ sources , err := decodeToBeParsed (blob , allAnimeKey )
517537 require .NoError (t , err )
518538 require .Len (t , sources , 1 )
519539 assert .Equal (t , "Fallback1" , sources [0 ].sourceName )
@@ -526,7 +546,7 @@ func TestDecodeToBeParsedRegexFallbackReversedFieldOrder(t *testing.T) {
526546 plaintext := `[{"sourceName":"Reversed","sourceUrl":"--0a0b"}]`
527547 blob := encryptToBeParsed (t , plaintext )
528548
529- sources , err := decodeToBeParsed (blob )
549+ sources , err := decodeToBeParsed (blob , allAnimeKey )
530550 require .NoError (t , err )
531551 require .Len (t , sources , 1 )
532552 assert .Equal (t , "Reversed" , sources [0 ].sourceName )
@@ -542,7 +562,7 @@ func TestDecodeToBeParsedDeterministicWithFixedNonce(t *testing.T) {
542562 blob2 := encryptToBeParsedWithNonce (t , plaintext , nonce )
543563 assert .Equal (t , blob1 , blob2 , "same nonce + plaintext must produce same blob" )
544564
545- sources , err := decodeToBeParsed (blob1 )
565+ sources , err := decodeToBeParsed (blob1 , allAnimeKey )
546566 require .NoError (t , err )
547567 require .Len (t , sources , 1 )
548568 assert .Equal (t , "Det" , sources [0 ].sourceName )
@@ -558,7 +578,7 @@ func TestDecodeToBeParsedLargePayload(t *testing.T) {
558578 plaintext := `{"data":{"episode":{"sourceUrls":[` + strings .Join (entries , "," ) + `]}}}`
559579 blob := encryptToBeParsed (t , plaintext )
560580
561- sources , err := decodeToBeParsed (blob )
581+ sources , err := decodeToBeParsed (blob , allAnimeKey )
562582 require .NoError (t , err )
563583 assert .Len (t , sources , 100 )
564584}
@@ -573,7 +593,7 @@ func TestExtractSourceURLsHandlesToBeParsed(t *testing.T) {
573593 blob := encryptToBeParsed (t , plaintext )
574594 response := buildToBeParsedResponse (blob )
575595
576- urls := NewAllAnimeClient ( ).extractSourceURLs (response )
596+ urls := newTestClient ( "" ).extractSourceURLs (response )
577597 require .Len (t , urls , 1 )
578598 assert .Equal (t , "https://allanime.day/clock.json" , urls [0 ])
579599}
@@ -614,7 +634,7 @@ func TestExtractSourceURLsToBeParsedFallsBackToStandard(t *testing.T) {
614634 // Response has "tobeparsed" but with garbage blob; should fall back to sourceUrls
615635 response := `{"data":{"episode":{"tobeparsed":"not-valid-base64","sourceUrls":[{"sourceUrl":"--0809","sourceName":"Fallback"}]}}}`
616636
617- urls := NewAllAnimeClient ( ).extractSourceURLs (response )
637+ urls := newTestClient ( "" ).extractSourceURLs (response )
618638 require .Len (t , urls , 1 )
619639 assert .Equal (t , "01" , urls [0 ])
620640}
@@ -1868,7 +1888,7 @@ func TestDecodeToBeParsedCrossValidateWithOpenSSL(t *testing.T) {
18681888 blob := base64 .StdEncoding .EncodeToString (payload )
18691889
18701890 // Decrypt using production code
1871- sources , err := decodeToBeParsed (blob )
1891+ sources , err := decodeToBeParsed (blob , allAnimeKey )
18721892 require .NoError (t , err )
18731893 require .Len (t , sources , 1 )
18741894 assert .Equal (t , "TestProvider" , sources [0 ].sourceName )
@@ -1970,7 +1990,7 @@ func TestDecodeToBeParsedNoPanicOnMalformed(t *testing.T) {
19701990 t .Run (fmt .Sprintf ("input_%d" , i ), func (t * testing.T ) {
19711991 t .Parallel ()
19721992 // Must not panic
1973- _ , _ = decodeToBeParsed (input )
1993+ _ , _ = decodeToBeParsed (input , allAnimeKey )
19741994 })
19751995 }
19761996}
0 commit comments