fix(android upload): restore provider uploads

This commit is contained in:
Tommaso Casaburi
2026-05-01 22:52:54 +07:00
parent bc213ecea2
commit e2154b3edf
9 changed files with 696 additions and 144 deletions
@@ -0,0 +1,76 @@
package fivechan.android;
import static org.junit.Assert.*;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import androidx.core.content.FileProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Live provider integration test (emulator/device) for Android's native catbox uploader. */
@RunWith(AndroidJUnit4.class)
public class CatboxLiveUploadTest {
private static final long TEST_TIMEOUT_SEC = 30;
private static final String GENERATED_FILE_NAME = "catbox-gradient-320x320.png";
private Context appContext;
private Uri uploadUri;
@Before
public void setUp() throws Exception {
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
uploadUri = createGradientPngUri(appContext);
}
@Test
public void catbox_liveUpload_fromGeneratedPng_succeeds() {
MediaUploadResult result = CatboxUploader.upload(appContext, uploadUri, TEST_TIMEOUT_SEC);
assertNotNull("Expected catbox result", result);
assertTrue("Expected catbox upload success, got: " + result.error, result.success);
assertNotNull("Expected uploaded URL", result.url);
String normalizedUrl = result.url.toLowerCase();
assertTrue(
"Expected catbox file URL, got: " + result.url,
normalizedUrl.matches("https?://files\\.catbox\\.moe/.+\\.(png|jpg|jpeg|webp|gif)(?:[?#].*)?"));
}
private static Uri createGradientPngUri(Context context) throws IOException {
File dir = new File(context.getCacheDir(), "live-upload-test");
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("Unable to create cache directory: " + dir.getAbsolutePath());
}
File imageFile = new File(dir, GENERATED_FILE_NAME);
Bitmap bitmap = Bitmap.createBitmap(320, 320, Bitmap.Config.ARGB_8888);
for (int y = 0; y < 320; y++) {
for (int x = 0; x < 320; x++) {
bitmap.setPixel(
x,
y,
Color.rgb((x * 7) % 256, (y * 5) % 256, ((x + y) * 3) % 256));
}
}
try (FileOutputStream fos = new FileOutputStream(imageFile)) {
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)) {
throw new IOException("Failed to encode generated PNG");
}
} finally {
bitmap.recycle();
}
return FileProvider.getUriForFile(
context,
context.getPackageName() + ".fileprovider",
imageFile);
}
}
@@ -0,0 +1,123 @@
package fivechan.android;
import static org.junit.Assert.*;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import androidx.core.content.FileProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Live provider integration test (emulator/device) for Android's ImgBB WebView uploader. */
@RunWith(AndroidJUnit4.class)
public class ImgbbLiveUploadTest {
private static final long TEST_TIMEOUT_SEC = 120;
private static final String GENERATED_FILE_NAME = "imgbb-gradient-320x320.png";
private Context appContext;
private Uri uploadUri;
@Before
public void setUp() throws Exception {
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
uploadUri = createGradientPngUri(appContext);
}
@Test
public void imgbb_liveUpload_fromGeneratedPng_succeeds() throws Exception {
Intent launchIntent = new Intent(appContext, MainActivity.class);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(launchIntent);
Thread.sleep(1_000);
AtomicReference<MediaUploadResult> resultRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaUploadCallback callback =
result -> {
resultRef.set(result);
latch.countDown();
};
MediaUploadAutomationRunner runner =
new MediaUploadAutomationRunner(
appContext,
uploadUri,
GENERATED_FILE_NAME,
MediaUploadRecipes.PROVIDER_IMGBB,
callback);
runner.run();
assertTrue(
"Runner did not complete within " + TEST_TIMEOUT_SEC + "s",
latch.await(TEST_TIMEOUT_SEC, TimeUnit.SECONDS));
MediaUploadResult result = resultRef.get();
assertNotNull("Callback did not receive result", result);
assertTrue(
"Expected live imgbb upload success, got error="
+ result.error
+ " stage="
+ result.stage
+ " elapsedMs="
+ result.elapsedMs
+ " selector="
+ result.matchedSelectors
+ " retries="
+ result.triggerRetryCount,
result.success);
assertNotNull("Expected uploaded URL", result.url);
String normalizedUrl = result.url.toLowerCase();
assertTrue(
"Expected direct i.ibb.co URL, got: " + result.url,
normalizedUrl.contains("://i.ibb.co/"));
assertTrue(
"Expected direct image URL with extension, got: " + result.url,
normalizedUrl.matches(
"https?://i\\.ibb\\.co/.+\\.(jpg|jpeg|png|gif|webp|bmp|avif)(?:[?#].*)?"));
}
private static Uri createGradientPngUri(Context context) throws IOException {
File dir = new File(context.getCacheDir(), "live-upload-test");
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("Unable to create cache directory: " + dir.getAbsolutePath());
}
File imageFile = new File(dir, GENERATED_FILE_NAME);
Bitmap bitmap = Bitmap.createBitmap(320, 320, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
for (int y = 0; y < 320; y++) {
int red = Math.round((255f * y) / 319f);
int blue = 255 - red;
paint.setColor(Color.rgb(red, 64, blue));
canvas.drawLine(0, y, 319, y, paint);
}
try (FileOutputStream fos = new FileOutputStream(imageFile)) {
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)) {
throw new IOException("Failed to encode generated PNG");
}
} finally {
bitmap.recycle();
}
return FileProvider.getUriForFile(
context,
context.getPackageName() + ".fileprovider",
imageFile);
}
}
@@ -3,6 +3,7 @@ package fivechan.android;
import static org.junit.Assert.*;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
@@ -16,6 +17,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,7 +40,13 @@ public class ImgurLiveUploadTest {
}
@Test
@Ignore("Imgur upload is no longer supported on Android WebView; keep for manual diagnostics.")
public void imgur_liveUpload_fromGeneratedPng_succeeds() throws Exception {
Intent launchIntent = new Intent(appContext, MainActivity.class);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(launchIntent);
Thread.sleep(1_000);
AtomicReference<MediaUploadResult> resultRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
MediaUploadCallback callback =
@@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,13 +18,15 @@ import org.junit.runner.RunWith;
* Instrumentation tests for MediaUploadAutomationRunner against controlled HTML fixtures.
* Uses DataTransfer injection (no chooser). Simulates: delayed DOM, missing selectors,
* success URL extraction, blocked. Validates timeout/error classification (input_not_found,
* blocked/captcha, upload_timed_out). Runs on emulator or device; fixtures are deterministic.
* blocked/captcha, upload_timed_out). Kept for opt-in diagnostics because Android app uploads no
* longer use this WebView path.
*/
@Ignore("Imgur WebView upload is no longer supported on Android; keep for manual diagnostics.")
@RunWith(AndroidJUnit4.class)
public class MediaUploadAutomationRunnerTest {
private static final String FIXTURE_BASE = "file:///android_asset/fixtures/";
private static final long TEST_TIMEOUT_SEC = 15;
private static final long TEST_TIMEOUT_SEC = 25;
/** Minimal 1x1 PNG for fixture tests (DataTransfer injection). */
private static final byte[] SAMPLE_FILE_BYTES =