test: cover native forward request boundary

This commit is contained in:
kris
2026-03-28 08:33:08 +08:00
parent 13c67425ab
commit 200fc18210
2 changed files with 240 additions and 5 deletions

View File

@@ -33,8 +33,12 @@ public class BossApiClient {
private final String baseUrl;
public BossApiClient(Context context) {
this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
this.baseUrl = BuildConfig.BOSS_API_BASE_URL;
this(context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE), BuildConfig.BOSS_API_BASE_URL);
}
BossApiClient(SharedPreferences prefs, String baseUrl) {
this.prefs = prefs;
this.baseUrl = baseUrl;
}
public boolean hasSessionHints() {
@@ -264,7 +268,7 @@ public class BossApiClient {
}
private ApiResponse requestRaw(String method, String path, @Nullable String body, boolean expectProtected) throws IOException, JSONException {
HttpURLConnection connection = (HttpURLConnection) new URL(baseUrl + path).openConnection();
HttpURLConnection connection = openConnection(path);
connection.setRequestMethod(method);
connection.setConnectTimeout(12000);
connection.setReadTimeout(12000);
@@ -301,6 +305,10 @@ public class BossApiClient {
return new ApiResponse(statusCode, json == null ? new JSONObject() : json);
}
HttpURLConnection openConnection(String path) throws IOException {
return (HttpURLConnection) new URL(baseUrl + path).openConnection();
}
private JSONObject readJson(InputStream stream) throws IOException, JSONException {
if (stream == null) {
return new JSONObject();
@@ -339,7 +347,7 @@ public class BossApiClient {
}
}
private void rememberIdentity(JSONObject json) {
void rememberIdentity(JSONObject json) {
if (json == null) return;
JSONObject session = json.optJSONObject("session");
JSONObject source = session != null ? session : json;
@@ -370,7 +378,7 @@ public class BossApiClient {
.apply();
}
private String encode(String value) {
String encode(String value) {
return Uri.encode(value);
}

View File

@@ -0,0 +1,227 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import android.content.SharedPreferences;
import org.json.JSONObject;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class BossApiClientForwardingTest {
@Test
public void forwardProjectMessageWritesStructuredJsonBody() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/source/forwards"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
JSONObject payload = ForwardPayloads.build("single", "m1", java.util.List.of());
BossApiClient.ApiResponse response = apiClient.forwardProjectMessage("source", "target", payload);
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/source/forwards", apiClient.lastPath);
assertEquals("POST", connection.requestMethodValue);
assertEquals(
"{\"targetProjectId\":\"target\",\"mode\":\"single\",\"sourceMessageId\":\"m1\"}",
connection.requestBody()
);
}
private static final class RecordingBossApiClient extends BossApiClient {
private final RecordingConnection connection;
private String lastPath = "";
RecordingBossApiClient(RecordingConnection connection) {
super(new InMemorySharedPreferences(), "https://boss.hyzq.net");
this.connection = connection;
}
@Override
HttpURLConnection openConnection(String path) {
lastPath = path;
return connection;
}
@Override
String encode(String value) {
return value;
}
@Override
void rememberIdentity(JSONObject json) {
// JVM 单测只关心 request body不需要走 Android org.json 的身份恢复副作用。
}
}
private static final class RecordingConnection extends HttpURLConnection {
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream();
private final Map<String, String> requestHeaders = new HashMap<>();
private String requestMethodValue = "GET";
RecordingConnection(URL url) {
super(url);
}
@Override
public void disconnect() {}
@Override
public boolean usingProxy() {
return false;
}
@Override
public void connect() {}
@Override
public void setRequestMethod(String method) throws ProtocolException {
requestMethodValue = method;
}
@Override
public void setRequestProperty(String key, String value) {
requestHeaders.put(key, value);
}
@Override
public String getRequestProperty(String key) {
return requestHeaders.get(key);
}
@Override
public OutputStream getOutputStream() {
return requestBody;
}
@Override
public int getResponseCode() {
return 200;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream("{\"ok\":true}".getBytes(StandardCharsets.UTF_8));
}
String requestBody() {
return requestBody.toString(StandardCharsets.UTF_8);
}
}
private static final class InMemorySharedPreferences implements SharedPreferences {
private final Map<String, String> values = new HashMap<>();
@Override
public Map<String, ?> getAll() {
return Collections.unmodifiableMap(values);
}
@Override
public String getString(String key, String defValue) {
return values.getOrDefault(key, defValue);
}
@Override
public Set<String> getStringSet(String key, Set<String> defValues) {
throw new UnsupportedOperationException();
}
@Override
public int getInt(String key, int defValue) {
throw new UnsupportedOperationException();
}
@Override
public long getLong(String key, long defValue) {
throw new UnsupportedOperationException();
}
@Override
public float getFloat(String key, float defValue) {
throw new UnsupportedOperationException();
}
@Override
public boolean getBoolean(String key, boolean defValue) {
throw new UnsupportedOperationException();
}
@Override
public boolean contains(String key) {
return values.containsKey(key);
}
@Override
public Editor edit() {
return new Editor() {
@Override
public Editor putString(String key, String value) {
values.put(key, value);
return this;
}
@Override
public Editor remove(String key) {
values.remove(key);
return this;
}
@Override
public Editor clear() {
values.clear();
return this;
}
@Override
public void apply() {}
@Override
public boolean commit() {
return true;
}
@Override
public Editor putStringSet(String key, Set<String> values) {
throw new UnsupportedOperationException();
}
@Override
public Editor putInt(String key, int value) {
throw new UnsupportedOperationException();
}
@Override
public Editor putLong(String key, long value) {
throw new UnsupportedOperationException();
}
@Override
public Editor putFloat(String key, float value) {
throw new UnsupportedOperationException();
}
@Override
public Editor putBoolean(String key, boolean value) {
throw new UnsupportedOperationException();
}
};
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
}
}