corpus

Log | Files | Refs | README | LICENSE

commit 999c811e963a498ef34c4b4b0840ed17a3b8db87
parent 81d101a0dd916cdeed3664734be04a7789e0d63c
Author: mtmn <miro@haravara.org>
Date:   Thu, 23 Apr 2026 15:16:54 +0200

feat: add tests for endpoint

Diffstat:
Msrc/Cover.purs | 2+-
Mtest/Main.purs | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 143 insertions(+), 4 deletions(-)

diff --git a/src/Cover.purs b/src/Cover.purs @@ -179,7 +179,7 @@ serveCover serveNotFound cfg slug url res = do trySource s3cfg false { name, s3Key, findUrl } = do cached <- checkS3 s3cfg s3Key if cached then do - Log.info $ "Serving " <> name <> " cover from S3: " <> s3Key + Log.info $ "Serving " <> name <> " cover from cache: " <> s3Key liftEffect $ Metrics.incCoverRequest slug name "s3_hit" liftEffect $ serveS3Redirect s3cfg s3Key res pure true diff --git a/test/Main.purs b/test/Main.purs @@ -2,6 +2,7 @@ module Test.Main where import Prelude +import Unsafe.Coerce (unsafeCoerce) import Data.Argonaut (decodeJson, encodeJson, parseJson) import Data.Array (length) import Data.Either (Either(..)) @@ -11,11 +12,11 @@ import Test.Spec (describe, it) import Test.Spec.Assertions (shouldEqual, fail) import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Runner.Node (runSpecAndExitProcess) -import Types (Listen(..), ListenBrainzResponse(..), MbidMapping(..), Payload(..), Stats(..), StatsEntry(..), TrackMetadata(..)) -import Db (FilterField(..), connect, initDb, checkExists, upsertScrobble, getScrobbles, initReleaseMetadata, upsertReleaseMetadata, getStats, dbBaseName, getOldestTs, getUnenrichedMbids, getEmptyGenreMbids, getArtistReleasesByMbids, touchGenreCheckedAt) +import Types (Listen(..), ListenBrainzResponse(..), MbidMapping(..), Payload(..), Stats(..), StatsEntry(..), TrackMetadata(..), ListenBrainzSubmitPayload(..), ListenBrainzSubmitListen(..), ListenBrainzSubmitTrackMetadata(..), ListenBrainzAdditionalInfo(..)) +import Db (FilterField(..), connect, initDb, checkExists, upsertScrobble, getScrobbles, initReleaseMetadata, upsertReleaseMetadata, getStats, dbBaseName, getOldestTs, getUnenrichedMbids, getEmptyGenreMbids, getArtistReleasesByMbids, touchGenreCheckedAt, getOrCreateToken, getTokenUser) import Data.Argonaut.Core (Json) import Foreign.Object as Object -import Main (parseFilterField) +import Main (parseFilterField, submitListenToListen, findUserByToken) import Cover (sanitizeKey) import Sync (listenBrainzUrl, lastfmTrackToListen, parseLastfmResponse) import S3 (getS3Url) @@ -43,6 +44,144 @@ main = runSpecAndExitProcess [consoleReporter] do parseFilterField "unknown" `shouldEqual` Nothing parseFilterField "" `shouldEqual` Nothing + describe "ListenBrainz Submission" do + it "should decode a ListenBrainz submission payload" do + let jsonStr = """ + { + "listen_type": "single", + "payload": [ + { + "listened_at": 123456789, + "track_metadata": { + "track_name": "Song Name", + "artist_name": "Artist Name", + "release_name": "Album Name", + "additional_info": { + "release_mbid": "rel-mbid", + "artist_mbids": ["art-mbid"], + "recording_mbid": "rec-mbid" + } + } + } + ] + } + """ + let result = parseJson jsonStr >>= decodeJson + case result of + Right (ListenBrainzSubmitPayload { listenType, payload }) -> do + listenType `shouldEqual` "single" + length payload `shouldEqual` 1 + case payload of + [ListenBrainzSubmitListen { listenedAt, trackMetadata: ListenBrainzSubmitTrackMetadata m }] -> do + listenedAt `shouldEqual` Just 123456789 + m.trackName `shouldEqual` "Song Name" + m.artistName `shouldEqual` "Artist Name" + m.releaseName `shouldEqual` Just "Album Name" + case m.additionalInfo of + Just (ListenBrainzAdditionalInfo info) -> do + info.releaseMbid `shouldEqual` Just "rel-mbid" + Nothing -> do + fail "Expected additional_info" + _ -> do + fail "Expected 1 listen" + Left err -> do + fail $ "Decoding failed: " <> show err + + it "should convert ListenBrainzSubmitListen to Listen correctly" do + let submission = ListenBrainzSubmitListen + { listenedAt: Just 123456789 + , trackMetadata: ListenBrainzSubmitTrackMetadata + { trackName: "Song Name" + , artistName: "Artist Name" + , releaseName: Just "Album Name" + , additionalInfo: Just (ListenBrainzAdditionalInfo + { releaseMbid: Just "rel-mbid" + , artistMbids: Just ["art-mbid"] + , recordingMbid: Just "rec-mbid" + }) + } + } + case submitListenToListen "single" submission of + Just (Listen { listenedAt, trackMetadata: TrackMetadata m }) -> do + listenedAt `shouldEqual` Just 123456789 + m.trackName `shouldEqual` Just "Song Name" + m.artistName `shouldEqual` Just "Artist Name" + m.releaseName `shouldEqual` Just "Album Name" + m.mbidMapping `shouldEqual` Just (MbidMapping { releaseMbid: Just "rel-mbid", caaReleaseMbid: Just "rel-mbid" }) + Nothing -> do + fail "Conversion failed" + + it "should ignore playing_now listens" do + let submission = ListenBrainzSubmitListen + { listenedAt: Nothing + , trackMetadata: ListenBrainzSubmitTrackMetadata + { trackName: "Song Name" + , artistName: "Artist Name" + , releaseName: Nothing + , additionalInfo: Nothing + } + } + submitListenToListen "playing_now" submission `shouldEqual` Nothing + + describe "Token Authentication" do + it "should create and verify tokens" do + conn <- connect ":memory:" + initDb conn + mToken <- getOrCreateToken conn "user1" + case mToken of + Nothing -> do + fail "Failed to create token" + Just token -> do + mSlug <- getTokenUser conn token + mSlug `shouldEqual` Just "user1" + + mSlugWrong <- getTokenUser conn "wrong-token" + mSlugWrong `shouldEqual` Nothing + + it "should find user by token across multiple contexts" do + conn1 <- connect ":memory:" + initDb conn1 + conn2 <- connect ":memory:" + initDb conn2 + + mToken1 <- getOrCreateToken conn1 "user1" + mToken2 <- getOrCreateToken conn2 "user2" + + case mToken1, mToken2 of + Just token1, Just token2 -> do + let + dummyConfig = + { listenbrainzUser: Nothing + , lastfmUser: Nothing + , lastfmApiKey: Nothing + , discogsToken: Nothing + , cosineApiKey: Nothing + , databaseFile: "" + , s3Bucket: Nothing + , s3Region: "" + , awsAccessKeyId: Nothing + , awsSecretAccessKey: Nothing + , awsEndpointUrl: Nothing + , awsS3AddressingStyle: Nothing + , coverCacheEnabled: false + , backupEnabled: false + , backupIntervalHours: 0 + } + ctx1 = { conn: conn1, writeLock: unsafeCoerce unit, config: dummyConfig, slug: "user1", displayName: "User 1", enrichMetadataFiber: Nothing, backupFiber: Nothing } + ctx2 = { conn: conn2, writeLock: unsafeCoerce unit, config: dummyConfig, slug: "user2", displayName: "User 2", enrichMetadataFiber: Nothing, backupFiber: Nothing } + contexts = [ ctx1, ctx2 ] + + res1 <- findUserByToken contexts token1 + map _.slug res1 `shouldEqual` Just "user1" + + res2 <- findUserByToken contexts token2 + map _.slug res2 `shouldEqual` Just "user2" + + resNone <- findUserByToken contexts "invalid" + map _.slug resNone `shouldEqual` Nothing + _, _ -> do + fail "Failed to create tokens" + describe "Corpus Types" do describe "MbidMapping Codecs" do it "should roundtrip MbidMapping" do