···1313fmt: ## Run go fmt and cleanup whitespace
1414 go fmt ./...
1515 find internal/templates -type f \( -name "*.html" -o -name "*.xml" \) -exec sed -i '' 's/[ \t]*$$//' {} +
1616+ find tests -type f -name "*.sh" -exec sed -i '' 's/[ \t]*$$//' {} +
16171718GIT_COMMIT=$(shell git rev-parse --short HEAD)
1819LDFLAGS=-ldflags "-X tumble/internal/version.CommitHash=$(GIT_COMMIT)"
···6061load-fixtures: ## Load test fixtures
6162 ./tests/load_fixtures.sh
62636363-run-test: build ## Run the application with the test database
6464+run-test: kill build ## Run the application with the test database
6465 $(BUILD_DIR)/$(BINARY_NAME) conf/config-test.yaml
6566666767686868-test-db: build ## Create a fresh test database with fixtures
6969+test-db: kill build ## Create a fresh test database with fixtures
6970 ./tests/setup_test_db.sh
70717172help: ## Show this help message
···4141check_200 "/index.xml?dtype=rss"
4242check_content_type "/index.xml?dtype=rss" "text/xml"
43434444-# Optional: XML Validation if xmllint is present
4444+# Optional: XML Validation if xmllint is presen
4545if command -v xmllint &> /dev/null; then
4646 echo -n "Validating RSS XML structure... "
4747 curl -s "$BASE_URL/index.xml?dtype=rss" > rss_temp.xml
···88888989# 7. Delete Link Test (Create -> Delete -> Verify)
9090echo -n "Testing DELETE /irclink/ flow... "
9191-# Create a link first
9191+# Create a link firs
9292CREATE_OUT=$(curl -s "$BASE_URL/irclink/?user=testdel&url=http://delete-test.com&source=irc")
9393# Check if we got an ID (numeric)
9494if [[ "$CREATE_OUT" =~ ^[0-9]+$ ]]; then
9595 DEL_ID=$CREATE_OUT
9696- # Delete it
9696+ # Delete i
9797 DEL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/irclink/?id=$DEL_ID")
9898 if [ "$DEL_STATUS" == "200" ]; then
9999 # Verify it's gone
+44-8
tests/load_fixtures.sh
···66DB_PATH="${DB_PATH:-tumble.sqlite}"
77ADD_LINK_SCRIPT="./tests/add_link.sh"
8899+# Detect Driver from Config if not se
1010+if [ -z "$DRIVER" ]; then
1111+ if [ -f "conf/config.yaml" ]; then
1212+ DETECTED_DRIVER=$(grep "driver:" conf/config.yaml | awk '{print $2}')
1313+ if [ -n "$DETECTED_DRIVER" ]; then
1414+ DRIVER=$DETECTED_DRIVER
1515+ echo "Auto-detected driver: $DRIVER"
1616+ fi
1717+ fi
1818+fi
1919+920echo "Using BASE_URL: $BASE_URL"
1021echo "Using DB_PATH: $DB_PATH"
1122···1728# Image
1829$ADD_LINK_SCRIPT "pic_poster" "https://cdn.tinnies.club/accounts/avatars/109/626/500/076/902/223/original/2a4a0d1a4ce728c4.jpg"
19302020-# Mastodon Post
3131+# Mastodon Pos
2132$ADD_LINK_SCRIPT "social_butterfly" "https://fosstodon.org/@genebean/113945244453254504"
22332334# Standard Link
···2940# Imgur (Animated)
3041$ADD_LINK_SCRIPT "meme_lord" "https://imgur.com/only-one-jack-black-0qetp3u"
31423232-# Twitter Post
4343+# Twitter Pos
3344$ADD_LINK_SCRIPT "tweet_master" "https://x.com/jcockhren/status/1229101594505097216?s=20"
34453546# Wikipedia (Go)
···8293# Giphy
8394$ADD_LINK_SCRIPT "gif_master" "https://giphy.com/gifs/cant-hardly-wait-kW8mnYSNkUYKc"
84958585-echo "Loading backdated 'Hot Links' directly into DB..."
9696+# Kevin's Broken Twitter Link (Sad Path)
9797+# Use the specific broken ID if possible, but add_link auto-increments.
9898+# We will just assert on content behavior for "kevin".
9999+$ADD_LINK_SCRIPT "kevin" "https://twitter.com/darkuncle/status/1483507577174441985"
100100+101101+# Tester's Valid Twitter Link (Happy Path)
102102+$ADD_LINK_SCRIPT "tester" "https://twitter.com/jcockhren/status/1229101594505097216?s=20"
103103+86104echo "Loading backdated 'Hot Links' directly into DB..."
87105if [ "$DRIVER" == "mysql" ]; then
88106 MYSQL_HOST="${MYSQL_HOST:-localhost}"
89107 MYSQL_USER="${MYSQL_USER:-tumble}"
9090- # Defaulting to no password for local dev if not set, or prompt? Better to rely on .my.cnf or env var.
9191- # We will assume MYSQL_PASSWORD is set if needed or it's empty.
108108+ # Defaulting to no password for local dev if not se
109109+110110+ # Try to detect port from config if not se
111111+ if [ -z "$MYSQL_PORT" ] && [ -f "conf/config.yaml" ]; then
112112+ # simple grep for host: ip:por
113113+ HOST_LINE=$(grep "host:" conf/config.yaml | awk '{print $2}')
114114+ if [[ "$HOST_LINE" == *":"* ]]; then
115115+ MYSQL_PORT=${HOST_LINE#*:}
116116+ echo "Auto-detected MySQL Port: $MYSQL_PORT"
117117+ fi
118118+ fi
92119 MYSQL_PORT="${MYSQL_PORT:-3306}"
120120+93121 CMD="mysql -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER"
94122 if [ -n "$MYSQL_PASSWORD" ]; then
95123 CMD="$CMD -p$MYSQL_PASSWORD"
96124 fi
97125 # Use database from config or env?
9898- # We need the database name. Let's assume MYSQL_DATABASE env var or "tumble_test"
9999- DB_NAME="${MYSQL_DATABASE:-tumble_test}"
100100- $CMD "$DB_NAME" < tests/fixtures_hot_mysql.sql
126126+ DB_NAME="${MYSQL_DATABASE:-tumble}"
127127+ # Check config for database name too if possible
128128+ if [ -z "$MYSQL_DATABASE" ] && [ -f "conf/config.yaml" ]; then
129129+ DETECTED_DB=$(grep "database:" conf/config.yaml | awk '{print $2}')
130130+ if [ -n "$DETECTED_DB" ]; then
131131+ DB_NAME=$DETECTED_DB
132132+ fi
133133+ fi
134134+135135+ # Suppress password warning
136136+ $CMD "$DB_NAME" < tests/fixtures_hot_mysql.sql 2>/dev/null
101137else
102138 sqlite3 "$DB_PATH" < tests/fixtures_hot.sql
103139fi
+45
tests/poster_test.sh
···11+#!/bin/bash
22+BASE_URL="http://localhost:8080"
33+FAIL=0
44+55+echo "Starting Poster Tests against $BASE_URL..."
66+77+# Fetch page conten
88+CONTENT=$(curl -s "$BASE_URL/?poster=kevin")
99+1010+# Check we got conten
1111+if [[ -z "$CONTENT" ]]; then
1212+ echo "FAIL: No content returned"
1313+ exit 1
1414+fi
1515+1616+# Check for Item 79480 (The broken link item)
1717+if [[ "$CONTENT" != *"data-irc-link-id=\"79480\""* ]]; then
1818+ echo "FAIL: Item 79480 not found in output"
1919+ FAIL=1
2020+fi
2121+2222+# Check that 'twitter-tweet' class (Server Side Embed) IS present (We restored it)
2323+if [[ "$CONTENT" == *"class=\"twitter-tweet\""* ]]; then
2424+ echo "OK: Found hardcoded 'twitter-tweet' class."
2525+else
2626+ echo "FAIL: 'twitter-tweet' class NOT found (Server side embed missing)."
2727+ FAIL=1
2828+fi
2929+3030+# Check that 'og-preview' IS present (SuppressOG=false)
3131+# The template renders: <div class="og-preview" id="og-preview-{{.ID}}"></div>
3232+if [[ "$CONTENT" == *"id=\"og-preview-79480\""* ]]; then
3333+ echo "OK: og-preview-79480 found."
3434+else
3535+ echo "FAIL: og-preview-79480 NOT found (SuppressOG might still be true)."
3636+ FAIL=1
3737+fi
3838+3939+if [ $FAIL -eq 0 ]; then
4040+ echo "Poster tests passed!"
4141+ exit 0
4242+else
4343+ echo "Poster tests failed!"
4444+ exit 1
4545+fi
+25-25
tests/preview_test.sh
···44444545# --- REDDIT ---
4646# Valid: Should have rich metadata (provider_name or type)
4747-test_preview "Reddit Valid" \
4848- "https://www.reddit.com/r/valheim/comments/leqdj6/our_first_encounter_with_the_troll/" \
4747+test_preview "Reddit Valid"
4848+ "https://www.reddit.com/r/valheim/comments/leqdj6/our_first_encounter_with_the_troll/"
4949 '.provider_name == "Reddit" or .title != null'
50505151# Invalid: specific non-existent post.
···5656# For now, let's assume 'Invalid' means we check for absence of specific post content if possible,
5757# OR just that it doesn't crash. But user requested testing 404 logic.
5858# If preview_reddit.go fails, it falls back.
5959-test_preview "Reddit Invalid" \
6060- "https://www.reddit.com/r/valheim/comments/INVALID_ID_12345/" \
5959+test_preview "Reddit Invalid"
6060+ "https://www.reddit.com/r/valheim/comments/INVALID_ID_12345/"
6161 '.error != null or (.title | contains("Page not found") or contains("Reddit"))'
62626363# --- SPOTIFY ---
6464# Valid
6565-test_preview "Spotify Valid" \
6666- "https://open.spotify.com/episode/7makk4oTQel546B0PZlDM5" \
6565+test_preview "Spotify Valid"
6666+ "https://open.spotify.com/episode/7makk4oTQel546B0PZlDM5"
6767 '.provider_name == "Spotify" or .type == "rich"'
686869697070# Invalid
7171# Spotify returns a 200 OK on invalid tracks with a "Spotify - Web Player" title,
7272# so we expect a valid scrape fallback, not an error.
7373-test_preview "Spotify Invalid" \
7474- "https://open.spotify.com/track/INVALID_TRACK_ID" \
7373+test_preview "Spotify Invalid"
7474+ "https://open.spotify.com/track/INVALID_TRACK_ID"
7575 '.provider_name == "Spotify"'
76767777# --- IMGUR ---
7878# Valid
7979-test_preview "Imgur Valid" \
8080- "https://imgur.com/only-one-jack-black-0qetp3u" \
7979+test_preview "Imgur Valid"
8080+ "https://imgur.com/only-one-jack-black-0qetp3u"
8181 '.title != null'
82828383# Invalid
8484-test_preview "Imgur Invalid" \
8585- "https://imgur.com/gallery/INVALID_GALLERY_ID" \
8484+test_preview "Imgur Invalid"
8585+ "https://imgur.com/gallery/INVALID_GALLERY_ID"
8686 '.error != null or (.title | contains("Imgur"))'
87878888# --- YOUTUBE ---
8989# Valid
9090-test_preview "YouTube Valid" \
9191- "https://www.youtube.com/watch?v=dQw4w9WgXcQ" \
9090+test_preview "YouTube Valid"
9191+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
9292 '.provider_name == "YouTube"'
93939494# Invalid (Video Unavailable) - Special handling in preview.go returning status 404
9595-test_preview "YouTube Invalid" \
9696- "https://youtu.be/Ie_Wl9eNffE" \
9595+test_preview "YouTube Invalid"
9696+ "https://youtu.be/Ie_Wl9eNffE"
9797 '.status == 404 and .error == "Video Unavailable"'
98989999# --- TWITTER ---
100100# Valid - Returns empty object {} on success per preview_twitter.go
101101-test_preview "Twitter Valid" \
102102- "https://x.com/jcockhren/status/1229101594505097216" \
101101+test_preview "Twitter Valid"
102102+ "https://x.com/jcockhren/status/1229101594505097216"
103103 '. == {}'
104104105105# Invalid - Falls back to scrape. Twitter returns a generic page title / icon.
106106-test_preview "Twitter Invalid" \
107107- "https://x.com/jcockhren/status/0000000000000000000" \
108108- '.provider_name != null'
106106+test_preview "Twitter Invalid"
107107+ "https://x.com/jcockhren/status/0000000000000000000"
108108+ '.error == "Tweet Unavailable" and .status == 404'
109109110110# --- TIKTOK ---
111111# Valid
112112-test_preview "TikTok Valid" \
113113- "https://www.tiktok.com/@tiagogreis/video/6830059644233223429" \
112112+test_preview "TikTok Valid"
113113+ "https://www.tiktok.com/@tiagogreis/video/6830059644233223429"
114114 '.provider_name == "TikTok" or .type == "video"'
115115116116117117# Invalid - Falls back to scrape.
118118-test_preview "TikTok Invalid" \
119119- "https://www.tiktok.com/@user/video/1234567890123456789" \
118118+test_preview "TikTok Invalid"
119119+ "https://www.tiktok.com/@user/video/1234567890123456789"
120120 '.title | contains("TikTok")'
121121122122
+4-1
tests/setup_test_db.sh
···1212rm -f "$DB_PATH"
13131414# Start server
1515+# Explicitly set driver for load_fixtures
1616+export DRIVER="sqlite"
1717+1518# Start server
1619echo "Starting server (logging to tumble-test.log)..."
1720$BINARY "$CONFIG" > tumble-test.log 2>&1 &
···1922echo "Server PID: $PID"
20232124# Ensure cleanup
2222-trap "echo 'Stopping server...'; kill $PID || true" EXIT
2525+trap "echo 'Stopping server...'; kill $PID 2>/dev/null || true" EXIT
23262427# Wait for server to be ready
2528echo "Waiting for server to be ready on port $PORT..."
+83
tests/twitter_behavior_test.sh
···11+#!/bin/bash
22+BASE_URL="http://localhost:8080"
33+FAIL=0
44+55+echo "Starting Twitter Behavior Tests against $BASE_URL..."
66+77+# Load Fixtures
88+echo "Loading fixtures..."
99+./tests/load_fixtures.sh > /dev/null
1010+1111+# 1. HAPPY PATH: Valid Twee
1212+# We need a user/post that has a VALID twitter link.
1313+# Based on fixtures/logs, we might look for a known valid one or insert one if we had an insert script.
1414+# For now, let's assume one exists or we check the 'tester' user mentioned by the user.
1515+# "See localhost:8080 for posts from 'tester'"
1616+1717+echo -n "Checking for Valid Tweet Render (tester)... "
1818+CONTENT_VALID=$(curl -s "$BASE_URL/?poster=tester")
1919+2020+# Check for server-side widget code
2121+if [[ "$CONTENT_VALID" == *"class=\"twitter-tweet\""* ]]; then
2222+ echo "OK (Server-side widget found)"
2323+else
2424+ # It might be that 'tester' has no tweets, but user said "posts from tester... tweets no longer rendering".
2525+ # So we assume they exist.
2626+ echo "FAIL (Server-side widget MISSING for valid tweet)"
2727+ FAIL=1
2828+fi
2929+3030+# Check for og-preview hook (required for 404 check even on valid ones, though it does nothing if valid)
3131+if [[ "$CONTENT_VALID" == *"class=\"og-preview\""* ]]; then
3232+ echo "OK (og-preview hook found)"
3333+else
3434+ echo "FAIL (og-preview hook MISSING)"
3535+ FAIL=1
3636+fi
3737+3838+3939+# 2. SAD PATH: Broken Tweet (404)
4040+# We know 'kevin' has the broken one (ID 79480).
4141+echo -n "Checking for Broken Tweet Handling (kevin)... "
4242+CONTENT_BROKEN=$(curl -s "$BASE_URL/?poster=kevin")
4343+4444+# It SHOULD have the widget code initially (server-side doesn't know it's broken yet)
4545+if [[ "$CONTENT_BROKEN" == *"class=\"twitter-tweet\""* ]]; then
4646+ echo "OK (Server-side widget present initially)"
4747+else
4848+ echo "FAIL (Widget missing on broken link - strictly speaking it should be there, then removed by JS?)"
4949+ # Actually, in our logic, we ALWAYS render the widget if it looks like a tweet.
5050+ # The JS then runs ogpreview, gets 404, and REPLACES the content.
5151+ # So purely from curl (server response), the widget MUST be there.
5252+ FAIL=1
5353+fi
5454+5555+# It MUST have the og-preview hook
5656+if [[ "$CONTENT_BROKEN" == *"class=\"og-preview\""* ]]; then
5757+ echo "OK (og-preview hook present)"
5858+else
5959+ echo "FAIL (og-preview hook MISSING on broken link)"
6060+ FAIL=1
6161+fi
6262+6363+# 3. VERIFY CLIENT SIDE 404 (Simulated)
6464+# We can't easily run JS here, but we can verify the *endpoint* acting correctly.
6565+# Request ogpreview for the broken URL.
6666+BROKEN_URL="https://twitter.com/darkuncle/status/1483507577174441985"
6767+echo -n "Checking ogpreview response for broken URL... "
6868+PREVIEW_RESP=$(curl -s "$BASE_URL/ogpreview.cgi?url=$BROKEN_URL")
6969+7070+if [[ "$PREVIEW_RESP" == *"\"status\":404"* ]] || [[ "$PREVIEW_RESP" == *"\"status\": 404"* ]]; then
7171+ echo "OK (API returns 404 status)"
7272+else
7373+ echo "FAIL (API did not return 404 status: $PREVIEW_RESP)"
7474+ FAIL=1
7575+fi
7676+7777+if [ $FAIL -eq 0 ]; then
7878+ echo "All Twitter behavior tests passed!"
7979+ exit 0
8080+else
8181+ echo "Tests failed!"
8282+ exit 1
8383+fi