/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
import org.mozilla.android.sync.test.helpers.BaseTestStorageRequestDelegate;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockRecord;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.android.sync.test.helpers.WaitHelper;
import org.mozilla.gecko.sync.CredentialsSource;
import org.mozilla.gecko.sync.JSONRecordFetcher;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.repositories.FetchFailedException;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.Server11Repository;
import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
import org.mozilla.gecko.sync.repositories.StoreFailedException;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.stage.SafeConstrainedServer11Repository;
import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.simpleframework.http.ContentType;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import ch.boye.httpclientandroidlib.HttpEntity;
public class TestServer11RepositorySession implements CredentialsSource {
public class POSTMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
try {
String content = request.getContent();
System.out.println("Content:" + content);
} catch (IOException e) {
e.printStackTrace();
}
ContentType contentType = request.getContentType();
System.out.println("Content-Type:" + contentType);
super.handle(request, response, 200, "{success:[]}");
}
}
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT + "/";
static final String LOCAL_BASE_URL = TEST_SERVER + "1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/";
static final String LOCAL_REQUEST_URL = LOCAL_BASE_URL + "storage/bookmarks";
static final String LOCAL_INFO_BASE_URL = LOCAL_BASE_URL + "info/";
static final String LOCAL_COUNTS_URL = LOCAL_INFO_BASE_URL + "collection_counts";
// Corresponds to rnewman+atest1@mozilla.com, local.
static final String USERNAME = "n6ec3u5bee3tixzp2asys7bs6fve4jfw";
static final String USER_PASS = "n6ec3u5bee3tixzp2asys7bs6fve4jfw:password";
static final String SYNC_KEY = "eh7ppnb82iwr5kt3z3uyi5vr44";
// Few-second timeout so that our longer operations don't time out and cause spurious error-handling results.
private static final int SHORT_TIMEOUT = 10000;
@Override
public String credentials() {
return USER_PASS;
}
private HTTPServerTestHelper data = new HTTPServerTestHelper();
public class MockServer11RepositorySession extends Server11RepositorySession {
public MockServer11RepositorySession(Repository repository) {
super(repository);
}
public RecordUploadRunnable getRecordUploadRunnable() {
// TODO: implement upload delegate in the class, too!
return new RecordUploadRunnable(null, recordsBuffer, recordGuidsBuffer, byteCount);
}
public void enqueueRecord(Record r) {
super.enqueue(r);
}
public HttpEntity getEntity() {
return this.getRecordUploadRunnable().getBodyEntity();
}
}
public class TestSyncStorageRequestDelegate extends
BaseTestStorageRequestDelegate {
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
assertTrue(res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("X-Weave-Timestamp"));
BaseResource.consumeEntity(res);
data.stopHTTPServer();
}
}
@Test
public void test() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new POSTMockServer());
MockServer11RepositorySession session = new MockServer11RepositorySession(
null);
session.enqueueRecord(new MockRecord(Utils.generateGuid(), null, 0, false));
session.enqueueRecord(new MockRecord(Utils.generateGuid(), null, 0, false));
URI uri = new URI(LOCAL_REQUEST_URL);
SyncStorageRecordRequest r = new SyncStorageRecordRequest(uri);
TestSyncStorageRequestDelegate delegate = new TestSyncStorageRequestDelegate();
delegate._credentials = USER_PASS;
r.delegate = delegate;
r.post(session.getEntity());
}
@SuppressWarnings("static-method")
protected TrackingWBORepository getLocal(int numRecords) {
final TrackingWBORepository local = new TrackingWBORepository();
for (int i = 0; i < numRecords; i++) {
BookmarkRecord outbound = new BookmarkRecord("outboundFail" + i, "bookmarks", 1, false);
local.wbos.put(outbound.guid, outbound);
}
return local;
}
protected Exception doSynchronize(MockServer server) throws Exception {
final String COLLECTION = "test";
final TrackingWBORepository local = getLocal(100);
final Server11Repository remote = new Server11Repository(TEST_SERVER, USERNAME, COLLECTION, this);
KeyBundle collectionKey = new KeyBundle(USERNAME, SYNC_KEY);
Crypto5MiddlewareRepository cryptoRepo = new Crypto5MiddlewareRepository(remote, collectionKey);
cryptoRepo.recordFactory = new BookmarkRecordFactory();
final Synchronizer synchronizer = new ServerLocalSynchronizer();
synchronizer.repositoryA = cryptoRepo;
synchronizer.repositoryB = local;
data.startHTTPServer(server);
try {
Exception e = TestServerLocalSynchronizer.doSynchronize(synchronizer);
return e;
} finally {
data.stopHTTPServer();
}
}
@Test
public void testFetchFailure() throws Exception {
MockServer server = new MockServer(404, "error");
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(FetchFailedException.class, e.getClass());
}
@Test
public void testStorePostSuccessWithFailingRecords() throws Exception {
MockServer server = new MockServer(200, "{ modified: \" + " + Utils.millisecondsToDecimalSeconds(System.currentTimeMillis()) + ", " +
"success: []," +
"failed: { outboundFail2: [] } }");
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testStorePostFailure() throws Exception {
MockServer server = new MockServer() {
public void handle(Request request, Response response) {
if (request.getMethod().equals("POST")) {
this.handle(request, response, 404, "missing");
}
this.handle(request, response, 200, "success");
return;
}
};
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testConstraints() throws Exception {
MockServer server = new MockServer() {
public void handle(Request request, Response response) {
if (request.getMethod().equals("GET")) {
if (request.getPath().getPath().endsWith("/info/collection_counts")) {
this.handle(request, response, 200, "{\"bookmarks\": 5001}");
}
}
this.handle(request, response, 400, "NOOOO");
}
};
final JSONRecordFetcher countsFetcher = new JSONRecordFetcher(LOCAL_COUNTS_URL, this.credentials());
final SafeConstrainedServer11Repository remote = new SafeConstrainedServer11Repository(TEST_SERVER, USERNAME, "bookmarks", this, 5000, "sortindex", countsFetcher);
data.startHTTPServer(server);
final AtomicBoolean out = new AtomicBoolean(false);
// Verify that shouldSkip returns true due to a fetch of too large counts,
// rather than due to a timeout failure waiting to fetch counts.
try {
WaitHelper.getTestWaiter().performWait(
SHORT_TIMEOUT,
new Runnable() {
@Override
public void run() {
remote.createSession(new RepositorySessionCreationDelegate() {
@Override
public void onSessionCreated(RepositorySession session) {
out.set(session.shouldSkip());
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void onSessionCreateFailed(Exception ex) {
WaitHelper.getTestWaiter().performNotify(ex);
}
@Override
public RepositorySessionCreationDelegate deferredCreationDelegate() {
return this;
}
}, null);
}
});
assertTrue(out.get());
} finally {
data.stopHTTPServer();
}
}
}