android: add attachment composer flow
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class AttachmentComposerStateTest {
|
||||
@Test
|
||||
public void imageAttachments_requireConfirmationBeforeSending() {
|
||||
AttachmentComposerState.PendingAttachment attachment =
|
||||
new AttachmentComposerState.PendingAttachment(
|
||||
"image",
|
||||
"现场照片.png",
|
||||
"image/png",
|
||||
4096L,
|
||||
new byte[] {1, 2, 3}
|
||||
);
|
||||
|
||||
assertTrue(attachment.requiresConfirmation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void videoAttachments_requireConfirmationBeforeSending() {
|
||||
AttachmentComposerState.PendingAttachment attachment =
|
||||
new AttachmentComposerState.PendingAttachment(
|
||||
"video",
|
||||
"巡检录屏.mp4",
|
||||
"video/mp4",
|
||||
8192L,
|
||||
new byte[] {4, 5, 6}
|
||||
);
|
||||
|
||||
assertTrue(attachment.requiresConfirmation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileAttachments_doNotRequireConfirmation() {
|
||||
AttachmentComposerState.PendingAttachment attachment =
|
||||
new AttachmentComposerState.PendingAttachment(
|
||||
"file",
|
||||
"日报.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
16384L,
|
||||
new byte[] {7, 8, 9}
|
||||
);
|
||||
|
||||
assertFalse(attachment.requiresConfirmation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BossApiClientAttachmentTest {
|
||||
@Test
|
||||
public void uploadAttachment_postsMultipartBodyWithSourceType() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(
|
||||
new URL("https://boss.hyzq.net/api/v1/projects/project-1/attachments")
|
||||
);
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
|
||||
|
||||
BossApiClient.ApiResponse response = apiClient.uploadAttachment(
|
||||
"project-1",
|
||||
"现场照片.png",
|
||||
"image/png",
|
||||
new byte[] {1, 2, 3, 4},
|
||||
"image"
|
||||
);
|
||||
|
||||
assertEquals(200, response.statusCode);
|
||||
assertEquals("/api/v1/projects/project-1/attachments", apiClient.lastPath);
|
||||
assertEquals("POST", connection.requestMethodValue);
|
||||
assertTrue(connection.contentTypeValue.startsWith("multipart/form-data; boundary="));
|
||||
assertTrue(connection.requestBody().contains("name=\"sourceType\""));
|
||||
assertTrue(connection.requestBody().contains("\r\nimage\r\n"));
|
||||
assertTrue(connection.requestBody().contains("name=\"file\"; filename=\"现场照片.png\""));
|
||||
assertTrue(connection.requestBody().contains("Content-Type: image/png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void analyzeAttachment_postsToAnalyzeEndpoint() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(
|
||||
new URL("https://boss.hyzq.net/api/v1/projects/project-1/attachments/att-1/analyze")
|
||||
);
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
|
||||
|
||||
BossApiClient.ApiResponse response = apiClient.analyzeAttachment("project-1", "att-1");
|
||||
|
||||
assertEquals(200, response.statusCode);
|
||||
assertEquals("/api/v1/projects/project-1/attachments/att-1/analyze", apiClient.lastPath);
|
||||
assertEquals("POST", connection.requestMethodValue);
|
||||
assertEquals("{}", 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 单测不需要落 Android 侧身份缓存。
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
private String contentTypeValue = "";
|
||||
|
||||
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);
|
||||
if ("Content-Type".equalsIgnoreCase(key)) {
|
||||
contentTypeValue = 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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaderFields() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user