/*
* Copyright (c) 2009-2011 Dropbox Inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package com.dropbox.client2;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import org.junit.Test;
import com.dropbox.client2.DropboxAPI.Account;
import com.dropbox.client2.DropboxAPI.DropboxFileInfo;
import com.dropbox.client2.DropboxAPI.DropboxInputStream;
import com.dropbox.client2.DropboxAPI.DropboxLink;
import com.dropbox.client2.DropboxAPI.Entry;
import com.dropbox.client2.DropboxAPI.NameDetails;
import com.dropbox.client2.DropboxAPI.TeamInfo;
import com.dropbox.client2.DropboxAPI.ThumbFormat;
import com.dropbox.client2.DropboxAPI.ThumbSize;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.exception.DropboxPartialFileException;
import com.dropbox.client2.exception.DropboxServerException;
import com.dropbox.client2.session.AppKeyPair;
import com.dropbox.client2.session.WebOAuth2Session;
/**
* Unit test for simple App.
*/
public class DropboxAPITest {
private static DropboxAPI<?> api;
static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
private final static String TESTS_DIR = "/" + dateFormat.format(new Date());
private final File foo = new File("testfiles", "foo.txt");
private final File song = new File("testfiles", "dropbox_song.mp3");
private final File frog = new File("testfiles", "Costa Rican Frog.jpg");
static {
try {
AppKeyPair consumerTokenPair = new AppKeyPair("", ""); // Don't need this for OAuth 2
WebOAuth2Session session = new WebOAuth2Session(consumerTokenPair, System.getProperty("access_token"));
api = new DropboxAPI<WebOAuth2Session>(session);
} catch (Throwable t) {
t.printStackTrace();
assert false : "Total failure trying to start WebAuthSession." + t;
}
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
public void assertFile(Entry e, File f, String path){
assertEquals(e.bytes, f.length());
assertEquals(e.isDir, false);
assertEquals(e.path, path);
}
/**
* @return the suite of tests being tested
*/
@Test
public void accountInfo() throws Exception {
Account info = api.accountInfo();
assert info.country != null : "No country for account";
assert info.displayName != null : "No displayName for account";
assert info.email != null : "No email for account";
assert info.quota > 0 : "0 quota in account";
assert info.quotaNormal > 0 : "0 normal quota in account";
assert info.referralLink != null : "No referral link for account";
assert info.uid > 0 : "No uid for account";
boolean isOverQuota = info.quotaNormal + info.quotaShared > info.quota;
assert info.isOverQuota() == isOverQuota : "IsOverQuota failed";
TeamInfo teamInfo = info.teamInfo;
if (teamInfo != null) {
assert teamInfo.name != null : "No team name for account";
assert teamInfo.teamId != null : "No team id for account";
System.out.println("User is on team " + teamInfo.name);
} else {
System.out.println("User is not on a team");
}
// name info (English locale assumptions)
assert info.locale.equals("en") : "Unexpected locale";
NameDetails nameDetails = info.nameDetails;
assert nameDetails.givenName.equalsIgnoreCase(nameDetails.familiarName);
assert info.displayName.equals(nameDetails.givenName + " " + nameDetails.surname);
}
// Get metadata for a nonexistent directory
@Test
public void getMetadataBad() throws Exception {
try {
String path = TESTS_DIR + "iDoNotExist.txt";
Entry ent = api.metadata(path, 1000, null, true, null);
assert ent.isDeleted: "The " + path + " directory should not exist";
} catch (DropboxServerException e) {
if (e.error != DropboxServerException._404_NOT_FOUND) {
assert false: "Unexpected Dropbox Server Error: " + e.toString();
}
} catch (DropboxException e) {
assert false: "Unexpected Dropbox Error: " + e.toString();
}
}
// Create the /tests directory
@Test
public void createFolder() throws Exception {
String path = TESTS_DIR + "createdFolder";
Entry ent = api.createFolder(path);
assertEquals(ent.isDir, true);
}
// Try to create another one, and make sure we get an error
@Test
public void createFolderDupe() throws Exception {
try {
api.createFolder(TESTS_DIR);
assert false : "Able to create duplicate folder";
} catch (DropboxServerException e) {
if (e.error != DropboxServerException._403_FORBIDDEN) {
assert false: "Unexpected Dropbox Server Error: " + e.toString();
}
} catch (DropboxException e) {
assert false: "Unexpected Dropbox Error: " + e.toString();
}
}
// Get metadata for the new /tests directory
@Test
public void getMetadata() throws Exception {
String path = TESTS_DIR + "/metadatasong.mp3";
uploadFile(song, path);
api.metadata(path, 1000, null, true, null);
}
// Get metadata for the new /tests directory, saving the hash
@Test
public void getMetadataCached() throws Exception {
try {
Entry ent = api.metadata(TESTS_DIR, 1000, null, true, null);
api.metadata(TESTS_DIR, 1000, ent.hash, true, null);
assert false : "Directory should have been cached";
} catch (DropboxServerException e) {
if (e.error != DropboxServerException._304_NOT_MODIFIED) {
assert false: "Unexpected Dropbox Server Error: " + e.toString();
}
} catch (DropboxException e) {
assert false: "Unexpected Dropbox Error: " + e.toString();
}
}
// Put a simple text file into /tests/foo.txt
@Test
public void putFile() throws Exception {
assertPutFile(foo, TESTS_DIR + "/putfoo.txt");
assertPutFile(frog, TESTS_DIR + "/putfrog.jpg");
assertPutFile(song, TESTS_DIR + "/putsong.mp3");
}
public void assertPutFile(File src, String destPath) throws Exception{
FileInputStream fis = new FileInputStream(src);
Entry ent = api.putFile(destPath, fis, src.length(), null, null);
assertEquals(ent.path, destPath);
assertEquals(ent.bytes, src.length());
assertEquals(ent.isDir, false);
}
@Test
public void getFile() throws Exception {
assertGetFile(foo, TESTS_DIR + "/getfoo.txt");
assertGetFile(song, TESTS_DIR + "/getsong.mp3");
assertGetFile(frog, TESTS_DIR + "/getfrog.jpg");
}
public void assertGetFile(File src, String destPath) throws Exception{
uploadFile(src, destPath);
byte[] downloaded = Util.streamToBytes(api.getFileStream(destPath, null));
byte[] local = Util.streamToBytes(new FileInputStream(src));
assertArrayEquals(downloaded, local);
}
@Test
public void getThumbnail() throws Exception {
String path = TESTS_DIR + "/thumbnailfrog.jpg";
uploadFile(frog, path);
OutputStream out = new ByteArrayOutputStream();
DropboxFileInfo info = api.getThumbnail(path, out,
ThumbSize.BESTFIT_480x320, ThumbFormat.JPEG, null);
assert info != null : "No info returned";
assert info.getFileSize() > 0 : "Thumbnail length 0";
assert info.getMetadata().bytes > 0 : "Original file size 0";
assert info.getFileSize() != info.getMetadata().bytes : "Thumbnail equals original file size.";
}
@Test
public void getThumbnailStream() throws Exception {
String path = TESTS_DIR + "/thumbnailstreamfrog.jpg";
uploadFile(frog, path);
DropboxInputStream is = api.getThumbnailStream(path, ThumbSize.BESTFIT_480x320, ThumbFormat.JPEG);
assert is != null : "No info returned";
assert is.getFileInfo().getFileSize() > 0 : "Thumbnail length 0";
assert is.getFileInfo().getMetadata().bytes > 0 : "Original file size 0";
}
private String createRandomContents(Random rng, int length, String characters) {
char[] text = new char[length];
for (int i = 0; i < length; i++)
{
text[i] = characters.charAt(rng.nextInt(characters.length()));
}
return new String(text);
}
// Only enable this test if you want to test the behavior of skipping. This
// was written to test a bug report that came in, but it doesn't look at
// this time that this is worthy of testing regularly.
//@Test
public void skipsCorrectly() throws Exception {
String path = TESTS_DIR + "/randomtext.txt";
// length of file in bytes to generate
int length = 1000000;
// number of bytes to skip
int skipCount = 950000;
// number of bytes after the skip offset to use to verify the correctness
// of the skip function
int bytesToVerify = 300;
String contents = createRandomContents(new Random(), length, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
// create input stream from random byte string
InputStream fis = new ByteArrayInputStream(contents.getBytes("US-ASCII"));
// put & get same file
api.putFileOverwrite(path, fis, contents.length(), null);
DropboxInputStream is = api.getFileStream(path, null);
// skip bytes
long amtSkipped = is.skip(skipCount);
byte[] outBytes = new byte[bytesToVerify];
is.read(outBytes, 0, bytesToVerify);
assertEquals(new String(outBytes), contents.substring((int)amtSkipped, (int)amtSkipped+bytesToVerify));
}
private Entry putRandomFile(String destPath, String parentRev) throws Exception {
int len = 10;
String data = createRandomContents(new Random(), len,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
InputStream in = new ByteArrayInputStream(data.getBytes("US-ASCII"));
Entry ent = api.putFile(destPath, in, len, parentRev, null);
assertEquals(ent.bytes, len);
assertEquals(ent.isDir, false);
return ent;
}
private Entry putRandomFile(String destPath, String parentRev, boolean autoRename) throws Exception {
int len = 10;
String data = createRandomContents(new Random(), len,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
InputStream in = new ByteArrayInputStream(data.getBytes("US-ASCII"));
return api.putFile(destPath, in, len, parentRev, autoRename, null);
}
@Test
public void testConflict() throws Exception {
String fname = TESTS_DIR + "/psingle.txt";
Entry baseres = putRandomFile(fname, null);
assert baseres.path.equals(fname) : ("unexpected changed file " + baseres.path + " " + fname);
// auto rename
Entry res = putRandomFile(fname, null);
assert !res.path.equals(fname) : "unexpected same file!";
// ditto
res = putRandomFile(fname, null, true);
assert !res.path.equals(fname) : "unexpected same file!";
// blow up
try {
putRandomFile(fname, null, false);
assert false : "Conflict should have been raised";
} catch (DropboxServerException e) {
if (e.error != DropboxServerException._409_CONFLICT) {
assert false: "Unexpected Dropbox Server Error: " + e.toString();
}
}
}
public void uploadFileOverwrite(File src, String target) throws Exception{
FileInputStream fis = new FileInputStream(src);
api.putFileOverwrite(target, fis, src.length(), null);
}
public void uploadFile(File src, String target) throws Exception{
FileInputStream fis = new FileInputStream(src);
api.putFile(target, fis, src.length(), null, null);
}
@Test
public void copy() throws Exception
{
String fromPath = TESTS_DIR + "/copyFrom.jpg";
String toPath = TESTS_DIR + "/copyTo.jpg";
uploadFile(frog, fromPath);
Entry ent = api.copy(fromPath, toPath);
assertFile(ent, frog, toPath);
ent = api.metadata(toPath, 1, null, false, null);
assertFile(ent, frog, toPath);
}
@Test
public void move() throws Exception
{
String fromPath = TESTS_DIR + "/moveFrom.jpg";
String toPath = TESTS_DIR + "/moveTo.jpg";
uploadFile(frog, fromPath);
Entry moveEnt = api.move(fromPath, toPath);
assertFile(moveEnt, frog, toPath);
moveEnt = api.metadata(toPath, 1, null, false, null);
assertFile(moveEnt, frog, toPath);
Entry oldEnt = api.metadata(fromPath, 1, null, false, null);
assertEquals(oldEnt.isDeleted, true);
assertEquals(oldEnt.bytes, 0);
assert oldEnt.isDeleted;
}
@Test
public void media() throws Exception {
String path = TESTS_DIR + "/dropbox_song.mp3";
uploadFile(song, path);
DropboxLink link = api.media(path, true);
assert link.url != null : "No url for streamed file";
link = api.media(path, false);
assert link.url != null : "No insecure url for streamed file";
}
@Test
public void share() throws Exception{
String path = TESTS_DIR + "/dropbox_song.mp3";
uploadFile(song, path);
DropboxLink link = api.share(path);
assert link.url != null : "No url for streamed file";
}
@Test
public void search() throws Exception {
try {
// Should be too short, and give an error
api.search("/", "", 1000, false);
assert false: "Short (empty) search string did not throw an error.";
} catch (DropboxServerException e) {
if (e.error != DropboxServerException._400_BAD_REQUEST) {
assert false: "Unexpected Dropbox Error: " + e.toString();
}
}
String searchDir = TESTS_DIR + "/searchDir/";
uploadFile(foo, searchDir + "text.txt");
uploadFile(foo, searchDir + "subFolder/text.txt");
uploadFile(foo, searchDir + "subFolder/cow.txt");
uploadFile(frog, searchDir + "frog.jpg");
uploadFile(frog, searchDir + "frog2.jpg");
uploadFile(frog, searchDir + "subFolder/frog2.jpg");
List<Entry> results = api.search(searchDir, "sadfasdf", 1000, false);
assertEquals(results.size(), 0);
results = api.search(searchDir, "jpg", 1000, false);
assertEquals(results.size(), 3);
HashSet<String> resultPaths = new HashSet<String>();
for (DropboxAPI.Entry e : results) {
assertEquals(e.bytes, frog.length());
assertEquals(e.isDir, false);
resultPaths.add(e.path);
}
resultPaths.equals(new HashSet<String>(Arrays.asList(
searchDir + "frog.jpg",
searchDir + "frog2.jpg",
searchDir + "subFolder/frog2.jpg")));
results = api.search(searchDir + "subFolder", "jpg", 1000, false);
assertEquals(results.size(), 1);
assertFile(results.get(0), frog, searchDir + "subFolder/frog2.jpg");
}
@Test
public void revisionsAndRestore() throws Exception {
String path = TESTS_DIR + "/revfoo.txt";
uploadFileOverwrite(foo, path);
uploadFileOverwrite(frog, path);
uploadFileOverwrite(foo, path);
List<Entry> revs = api.revisions(path, 1000);
assert revs.size() == 3 : "Not enough revs for file";
assertFile(revs.get(0), foo, path);
assertFile(revs.get(1), frog, path);
assertFile(revs.get(2), foo, path);
for (Entry rev: revs) {
System.out.println("Revision: " + rev.rev + " modified: " + rev.modified);
}
Entry reverted = api.restore(path, revs.get(1).rev);
assertFile(reverted, frog, path);
}
@Test
public void chunkedUploadPlain() throws Exception {
chunkedUploadsTestBase("/cu1.dat", 10*1024*1024, 10*1024*1024, 4000000, false);
}
@Test
public void chunkedUploadStream() throws Exception {
chunkedUploadsTestBase("/cu2.dat", 2*1024*1024, -1, 4000000, false);
}
@Test
public void chunkedUploadStreamWithoutLeftover() throws Exception {
chunkedUploadsTestBase("/cu3.dat", 2*1024*1024, -1, 1024*1024, false);
}
@Test
public void chunkedUploadStreamOverrun() throws Exception {
try {
chunkedUploadsTestBase("/cu4.dat", 1024 * 1024, 1024 * 1024 + 1, 1024 * 1024, false);
assert false : "Expected IllegalStateException due to insufficient data in input stream";
} catch (IllegalStateException e) {
}
}
@Test
public void chunkedUploadAbort() throws Exception {
try {
chunkedUploadsTestBase("/cu5.dat", 10*1024*1024, 10*1024*1024, 4000000, true);
assert false: "No exception???";
} catch (DropboxPartialFileException d) {
}
}
private void chunkedUploadsTestBase(String filename, int dataSizeInBytes, int bytesToUpload, int chunkSize, boolean abort) throws Exception{
String path = TESTS_DIR + filename;
Random r = new Random();
byte[] bytes = new byte[dataSizeInBytes];
r.nextBytes(bytes);
System.out.println("Uploading");
final boolean[] progressListenerFired = {false};
final DropboxAPI.ChunkedUploader uploader = api.getChunkedUploader(new ByteArrayInputStream(bytes), bytesToUpload, chunkSize);
if (abort) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("KILLING");
uploader.abort();
} catch(Exception e) {
throw new AssertionError(e);
}
}
}).start();
}
while(!uploader.isComplete()) {
try {
uploader.upload(new ProgressListener(){
@Override
public void onProgress(long bytes, long total) {
System.out.println("progress: " + bytes);
progressListenerFired[0] = true;
}
});
} catch (Exception e) {
System.out.println(uploader.getOffset());
if (!uploader.getActive()) {
throw e;
}
throw e;
}
}
uploader.finish(path, null);
DropboxAPI.DropboxInputStream byteStream = api.getFileStream(path, null);
byte[] returnedBytes = Util.streamToBytes(byteStream);
assertTrue(progressListenerFired[0]);
assertEquals(bytes.length, returnedBytes.length);
assertArrayEquals(returnedBytes, bytes);
}
}