mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-09-06 18:01:11 +00:00
Re-enable tests again
This commit is contained in:
parent
e7f610c150
commit
e6573ed4c1
|
@ -8,9 +8,6 @@ android {
|
||||||
applicationId "at.bitfire.davdroid"
|
applicationId "at.bitfire.davdroid"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 21
|
targetSdkVersion 21
|
||||||
|
|
||||||
testApplicationId "at.bitfire.davdroid.test"
|
|
||||||
testInstrumentationRunner "android.test.InstrumentationTestRunner"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
11
app/src/androidTest/assets/all-day-0sec.ics
Normal file
11
app/src/androidTest/assets/all-day-0sec.ics
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:all-day-0sec@example.com
|
||||||
|
DTSTAMP:20140101T000000Z
|
||||||
|
DTSTART;VALUE=DATE:19970714
|
||||||
|
DTEND;VALUE=DATE:19970714
|
||||||
|
SUMMARY:0 Sec Event
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
11
app/src/androidTest/assets/all-day-10days.ics
Normal file
11
app/src/androidTest/assets/all-day-10days.ics
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:all-day-10days@example.com
|
||||||
|
DTSTAMP:20140101T000000Z
|
||||||
|
DTSTART;VALUE=DATE:19970714
|
||||||
|
DTEND;VALUE=DATE:19970724
|
||||||
|
SUMMARY:All-Day 10 Days
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
11
app/src/androidTest/assets/all-day-1day.ics
Normal file
11
app/src/androidTest/assets/all-day-1day.ics
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:all-day-1day@example.com
|
||||||
|
DTSTAMP:20140101T000000Z
|
||||||
|
DTSTART;VALUE=DATE:19970714
|
||||||
|
DTEND;VALUE=DATE:19970714
|
||||||
|
SUMMARY:All-Day 1 Day
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
11
app/src/androidTest/assets/event-on-that-day.ics
Normal file
11
app/src/androidTest/assets/event-on-that-day.ics
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:event-on-that-day@example.com
|
||||||
|
DTSTAMP:19970714T170000Z
|
||||||
|
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
|
||||||
|
DTSTART;VALUE=DATE:19970714
|
||||||
|
SUMMARY:Bastille Day Party
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
9
app/src/androidTest/assets/impp.vcf
Normal file
9
app/src/androidTest/assets/impp.vcf
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
UID:2de59c6cc9
|
||||||
|
PRODID:-//ownCloud//NONSGML Contacts 0.2.5//EN
|
||||||
|
REV:2013-12-08T00:04:30+00:00
|
||||||
|
FN:test mctest
|
||||||
|
N:mctest;test;;;
|
||||||
|
IMPP;TYPE=WORK;X-SERVICE-TYPE=jabber:test-without-valid-scheme@test.tld
|
||||||
|
END:VCARD
|
|
@ -0,0 +1,5 @@
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
FN:VCard with invalid unknown properties
|
||||||
|
X-UNKNOWN@PROPERTY:MUST-NOT_CONTAIN?OTHER*LETTERS;
|
||||||
|
END:VCARD
|
16
app/src/androidTest/assets/reference.vcf
Normal file
16
app/src/androidTest/assets/reference.vcf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
N:Gump;Forrest;Mr.
|
||||||
|
FN:Forrest Gump
|
||||||
|
ORG:Bubba Gump Shrimp Co.
|
||||||
|
TITLE:Shrimp Man
|
||||||
|
PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif
|
||||||
|
TEL;TYPE=WORK,VOICE:(111) 555-1212
|
||||||
|
TEL;TYPE=HOME,VOICE:(404) 555-1212
|
||||||
|
ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
|
||||||
|
LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America
|
||||||
|
ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
|
||||||
|
LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America
|
||||||
|
EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
|
||||||
|
REV:2008-04-24T19:52:43Z
|
||||||
|
END:VCARD
|
BIN
app/src/androidTest/assets/test.random
Normal file
BIN
app/src/androidTest/assets/test.random
Normal file
Binary file not shown.
16
app/src/androidTest/assets/vcard3-sample1.vcf
Normal file
16
app/src/androidTest/assets/vcard3-sample1.vcf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
N:Gump;Forrest
|
||||||
|
FN:Forrest Gump
|
||||||
|
ORG:Bubba Gump Shrimp Co.
|
||||||
|
TITLE:Shrimp Man
|
||||||
|
PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif
|
||||||
|
TEL;TYPE=WORK,VOICE:(111) 555-1212
|
||||||
|
TEL;TYPE=HOME,VOICE:(404) 555-1212
|
||||||
|
ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
|
||||||
|
LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America
|
||||||
|
ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
|
||||||
|
LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America
|
||||||
|
EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
|
||||||
|
REV:2008-04-24T19:52:43Z
|
||||||
|
END:VCARD
|
33
app/src/androidTest/assets/vienna-evolution.ics
Normal file
33
app/src/androidTest/assets/vienna-evolution.ics
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Vienna
|
||||||
|
X-LIC-LOCATION:Europe/Vienna
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701027T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700331T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:c252087c-7354-4722-aea9-0e7d86c01a25
|
||||||
|
DTSTAMP:20130926T151211Z
|
||||||
|
SUMMARY:Test-Ereignis im schönen Wien
|
||||||
|
DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000
|
||||||
|
DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T180000
|
||||||
|
X-RADICALE-NAME:97929342-291a-434e-bf1a-fa1749bf99d0.ics
|
||||||
|
X-EVOLUTION-CALDAV-HREF:/radicale/rfc2822/default.ics/97929342-291a-434e-bf1a-fa1749bf99d0.ics
|
||||||
|
X-EVOLUTION-CALDAV-ETAG:\"-3264224243575339985\"
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -5,7 +5,7 @@
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package at.bitfire.davdroid.test;
|
package at.bitfire.davdroid;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
|
@ -5,7 +5,7 @@
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package at.bitfire.davdroid.test;
|
package at.bitfire.davdroid;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
|
@ -1,11 +1,11 @@
|
||||||
package at.bitfire.davdroid.test;
|
package at.bitfire.davdroid;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
public class Constants {
|
public class TestConstants {
|
||||||
public static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
|
public static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
|
||||||
|
|
||||||
public static URI roboHydra;
|
public static URI roboHydra;
|
|
@ -5,7 +5,7 @@
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package at.bitfire.davdroid.test;
|
package at.bitfire.davdroid;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import android.test.InstrumentationTestCase;
|
||||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||||
import at.bitfire.davdroid.resource.ServerInfo;
|
import at.bitfire.davdroid.resource.ServerInfo;
|
||||||
import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo;
|
import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo;
|
||||||
import at.bitfire.davdroid.test.Constants;
|
import at.bitfire.davdroid.TestConstants;
|
||||||
import ezvcard.VCardVersion;
|
import ezvcard.VCardVersion;
|
||||||
|
|
||||||
public class DavResourceFinderTest extends InstrumentationTestCase {
|
public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||||
|
@ -27,7 +27,7 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
|
|
||||||
public void testFindResourcesRobohydra() throws Exception {
|
public void testFindResourcesRobohydra() throws Exception {
|
||||||
ServerInfo info = new ServerInfo(new URI(Constants.ROBOHYDRA_BASE), "test", "test", true);
|
ServerInfo info = new ServerInfo(new URI(TestConstants.ROBOHYDRA_BASE), "test", "test", true);
|
||||||
finder.findResources(info);
|
finder.findResources(info);
|
||||||
|
|
||||||
// CardDAV
|
// CardDAV
|
||||||
|
@ -56,9 +56,9 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
public void testGetInitialContextURL() throws Exception {
|
public void testGetInitialContextURL() throws Exception {
|
||||||
// without SRV records, but with well-known paths
|
// without SRV records, but with well-known paths
|
||||||
ServerInfo roboHydra = new ServerInfo(new URI(Constants.ROBOHYDRA_BASE), "test", "test", true);
|
ServerInfo roboHydra = new ServerInfo(new URI(TestConstants.ROBOHYDRA_BASE), "test", "test", true);
|
||||||
assertEquals(Constants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "caldav"));
|
assertEquals(TestConstants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "caldav"));
|
||||||
assertEquals(Constants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "carddav"));
|
assertEquals(TestConstants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "carddav"));
|
||||||
|
|
||||||
// with SRV records and well-known paths
|
// with SRV records and well-known paths
|
||||||
ServerInfo iCloud = new ServerInfo(new URI("mailto:test@icloud.com"), "", "", true);
|
ServerInfo iCloud = new ServerInfo(new URI("mailto:test@icloud.com"), "", "", true);
|
||||||
|
|
|
@ -3,14 +3,14 @@ package at.bitfire.davdroid.webdav;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
import at.bitfire.davdroid.test.Constants;
|
import at.bitfire.davdroid.TestConstants;
|
||||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import ch.boye.httpclientandroidlib.client.methods.HttpOptions;
|
import org.apache.http.client.methods.HttpOptions;
|
||||||
import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
|
||||||
public class DavRedirectStrategyTest extends TestCase {
|
public class DavRedirectStrategyTest extends TestCase {
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ public class DavRedirectStrategyTest extends TestCase {
|
||||||
// happy cases
|
// happy cases
|
||||||
|
|
||||||
public void testNonRedirection() throws Exception {
|
public void testNonRedirection() throws Exception {
|
||||||
HttpUriRequest request = new HttpOptions(Constants.roboHydra);
|
HttpUriRequest request = new HttpOptions(TestConstants.roboHydra);
|
||||||
HttpResponse response = httpClient.execute(request);
|
HttpResponse response = httpClient.execute(request);
|
||||||
assertFalse(strategy.isRedirected(request, response, null));
|
assertFalse(strategy.isRedirected(request, response, null));
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,12 @@ public class DavRedirectStrategyTest extends TestCase {
|
||||||
final String newLocation = "/new-location";
|
final String newLocation = "/new-location";
|
||||||
|
|
||||||
HttpContext context = HttpClientContext.create();
|
HttpContext context = HttpClientContext.create();
|
||||||
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/301?to=" + newLocation));
|
HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/301?to=" + newLocation));
|
||||||
HttpResponse response = httpClient.execute(request, context);
|
HttpResponse response = httpClient.execute(request, context);
|
||||||
assertTrue(strategy.isRedirected(request, response, context));
|
assertTrue(strategy.isRedirected(request, response, context));
|
||||||
|
|
||||||
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
|
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
|
||||||
assertEquals(Constants.roboHydra.resolve(newLocation), redirected.getURI());
|
assertEquals(TestConstants.roboHydra.resolve(newLocation), redirected.getURI());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,18 +56,18 @@ public class DavRedirectStrategyTest extends TestCase {
|
||||||
|
|
||||||
public void testMissingLocation() throws Exception {
|
public void testMissingLocation() throws Exception {
|
||||||
HttpContext context = HttpClientContext.create();
|
HttpContext context = HttpClientContext.create();
|
||||||
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/without-location"));
|
HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/without-location"));
|
||||||
HttpResponse response = httpClient.execute(request, context);
|
HttpResponse response = httpClient.execute(request, context);
|
||||||
assertFalse(strategy.isRedirected(request, response, context));
|
assertFalse(strategy.isRedirected(request, response, context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRelativeLocation() throws Exception {
|
public void testRelativeLocation() throws Exception {
|
||||||
HttpContext context = HttpClientContext.create();
|
HttpContext context = HttpClientContext.create();
|
||||||
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/relative"));
|
HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/relative"));
|
||||||
HttpResponse response = httpClient.execute(request, context);
|
HttpResponse response = httpClient.execute(request, context);
|
||||||
assertTrue(strategy.isRedirected(request, response, context));
|
assertTrue(strategy.isRedirected(request, response, context));
|
||||||
|
|
||||||
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
|
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
|
||||||
assertEquals(Constants.roboHydra.resolve("/new/location"), redirected.getURI());
|
assertEquals(TestConstants.roboHydra.resolve("/new/location"), redirected.getURI());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
import at.bitfire.davdroid.test.Constants;
|
import at.bitfire.davdroid.TestConstants;
|
||||||
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
|
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
|
||||||
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
||||||
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
|
||||||
// tests require running robohydra!
|
// tests require running robohydra!
|
||||||
|
|
||||||
|
@ -46,15 +46,15 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
assetMgr = getInstrumentation().getContext().getResources().getAssets();
|
assetMgr = getInstrumentation().getContext().getResources().getAssets();
|
||||||
|
|
||||||
baseDAV = new WebDavResource(httpClient, Constants.roboHydra.resolve("/dav/"));
|
baseDAV = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("/dav/"));
|
||||||
|
|
||||||
simpleFile = new WebDavResource(httpClient, Constants.roboHydra.resolve("assets/test.random"));
|
simpleFile = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("assets/test.random"));
|
||||||
|
|
||||||
davCollection = new WebDavResource(httpClient, Constants.roboHydra.resolve("dav/"));
|
davCollection = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("dav/"));
|
||||||
davNonExistingFile = new WebDavResource(davCollection, "collection/new.file");
|
davNonExistingFile = new WebDavResource(davCollection, "collection/new.file");
|
||||||
davExistingFile = new WebDavResource(davCollection, "collection/existing.file");
|
davExistingFile = new WebDavResource(davCollection, "collection/existing.file");
|
||||||
|
|
||||||
davInvalid = new WebDavResource(httpClient, Constants.roboHydra.resolve("dav-invalid/"));
|
davInvalid = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("dav-invalid/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -227,7 +227,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
||||||
dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS);
|
dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS);
|
||||||
List<WebDavResource> members = dav.getMembers();
|
List<WebDavResource> members = dav.getMembers();
|
||||||
assertEquals(1, members.size());
|
assertEquals(1, members.size());
|
||||||
assertEquals(Constants.ROBOHYDRA_BASE + "dav-invalid/addressbooks/~user1/My%20Contacts:1.vcf/", members.get(0).getLocation().toASCIIString());
|
assertEquals(TestConstants.ROBOHYDRA_BASE + "dav-invalid/addressbooks/~user1/My%20Contacts:1.vcf/", members.get(0).getLocation().toASCIIString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
1
app/src/androidTest/robohydra/.gitignore
vendored
Normal file
1
app/src/androidTest/robohydra/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
6
app/src/androidTest/robohydra/davdroid.conf
Normal file
6
app/src/androidTest/robohydra/davdroid.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{"plugins":[
|
||||||
|
"assets",
|
||||||
|
"redirect",
|
||||||
|
"dav",
|
||||||
|
"dav-invalid"
|
||||||
|
]}
|
12
app/src/androidTest/robohydra/plugins/assets/index.js
Normal file
12
app/src/androidTest/robohydra/plugins/assets/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
var RoboHydraHeadFilesystem = require("robohydra").heads.RoboHydraHeadFilesystem;
|
||||||
|
|
||||||
|
exports.getBodyParts = function(conf) {
|
||||||
|
return {
|
||||||
|
heads: [
|
||||||
|
new RoboHydraHeadFilesystem({
|
||||||
|
mountPath: '/assets/',
|
||||||
|
documentRoot: '../assets'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
36
app/src/androidTest/robohydra/plugins/dav-invalid/index.js
Normal file
36
app/src/androidTest/robohydra/plugins/dav-invalid/index.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
var roboHydraHeadDAV = require("../headdav");
|
||||||
|
|
||||||
|
exports.getBodyParts = function(conf) {
|
||||||
|
return {
|
||||||
|
heads: [
|
||||||
|
/* address-book home set */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav-invalid/addressbooks/~user1/",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav-invalid/addressbooks/~user1/My%20Contacts:1.vcf/</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
<CARD:addressbook/>\
|
||||||
|
</resourcetype>\
|
||||||
|
<CARD:addressbook-description>\
|
||||||
|
Address Book with dubious characters in path\
|
||||||
|
</CARD:addressbook-description>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
278
app/src/androidTest/robohydra/plugins/dav/index.js
Normal file
278
app/src/androidTest/robohydra/plugins/dav/index.js
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
var roboHydraHeadDAV = require("../headdav");
|
||||||
|
|
||||||
|
exports.getBodyParts = function(conf) {
|
||||||
|
return {
|
||||||
|
heads: [
|
||||||
|
/* base URL, provide default DAV here */
|
||||||
|
new RoboHydraHeadDAV({ path: "/dav/" }),
|
||||||
|
|
||||||
|
/* multistatus parsing */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/collection-response-with-trailing-slash",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PROPFIND") {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/collection-response-with-trailing-slash/</href> \
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<current-user-principal>\
|
||||||
|
<href>/principals/ok</href>\
|
||||||
|
</current-user-principal>\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
</resourcetype>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/collection-response-without-trailing-slash",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PROPFIND") {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/collection-response-without-trailing-slash</href> \
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<current-user-principal>\
|
||||||
|
<href>/principals/ok</href>\
|
||||||
|
</current-user-principal>\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
</resourcetype>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* principal URL */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/principals/users/test",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PROPFIND" && req.rawBody.toString().match(/home-?set/)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/principals/users/t%65st</href> \
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<CARD:addressbook-home-set xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||||
|
<href>/dav/addressbooks/test</href>\
|
||||||
|
</CARD:addressbook-home-set>\
|
||||||
|
<CAL:calendar-home-set xmlns:CAL="urn:ietf:params:xml:ns:caldav">\
|
||||||
|
<href>/dav/calendars/test/</href>\
|
||||||
|
</CAL:calendar-home-set>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* address-book home set */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/addressbooks/test/",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (!req.url.match(/\/$/)) {
|
||||||
|
res.statusCode = 302;
|
||||||
|
res.headers['location'] = "/dav/addressbooks/test/";
|
||||||
|
}
|
||||||
|
else if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/addressbooks/test/useless-member</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<resourcetype/>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/addressbooks/test/default-v4.vcf/</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
<CARD:addressbook/>\
|
||||||
|
</resourcetype>\
|
||||||
|
<CARD:addressbook-description>Default Address Book</CARD:addressbook-description>\
|
||||||
|
<CARD:supported-address-data>\
|
||||||
|
<CARD:address-data-type content-type="text/vcard" version="3.0" />\
|
||||||
|
<CARD:address-data-type content-type="text/vcard" version="4.0" />\
|
||||||
|
</CARD:supported-address-data>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* calendar home set */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/calendars/test/",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PROPFIND" && req.rawBody.toString().match(/calendar-description/)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/calendars/test/shared.forbidden</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<resourcetype/>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 403 Forbidden</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/calendars/test/private.ics</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
<CAL:calendar/>\
|
||||||
|
</resourcetype>\
|
||||||
|
<displayname>Private Calendar</displayname>\
|
||||||
|
<CAL:calendar-description>This is my private calendar.</CAL:calendar-description>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/calendars/test/work.ics</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<resourcetype>\
|
||||||
|
<collection/>\
|
||||||
|
<CAL:calendar/>\
|
||||||
|
</resourcetype>\
|
||||||
|
<current-user-privilege-set>\
|
||||||
|
<privilege><read/></privilege>\
|
||||||
|
</current-user-privilege-set>\
|
||||||
|
<displayname>Work Calendar</displayname>\
|
||||||
|
<A:calendar-color xmlns:A="http://apple.com/ns/ical/">0xFF00FF</A:calendar-color>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* non-existing file */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/collection/new.file",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PUT") {
|
||||||
|
if (req.headers['if-match']) /* can't overwrite new file */
|
||||||
|
res.statusCode = 412;
|
||||||
|
else {
|
||||||
|
res.statusCode = 201;
|
||||||
|
res.headers["ETag"] = "has-just-been-created";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (req.method == "DELETE")
|
||||||
|
res.statusCode = 404;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* existing file */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/collection/existing.file",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "PUT") {
|
||||||
|
if (req.headers['if-none-match']) /* requested "don't overwrite", but this file exists */
|
||||||
|
res.statusCode = 412;
|
||||||
|
else {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.headers["ETag"] = "has-just-been-updated";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (req.method == "DELETE")
|
||||||
|
res.statusCode = 204;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
/* address-book multiget */
|
||||||
|
new RoboHydraHeadDAV({
|
||||||
|
path: "/dav/addressbooks/default.vcf/",
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
if (req.method == "REPORT" && req.rawBody.toString().match(/addressbook-multiget[\s\S]+<prop>[\s\S]+<href>/m)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/addressbooks/d%65fault.vcf</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<displayname>My Book</displayname>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/addressbooks/default.vcf/1.vcf</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<getetag/>\
|
||||||
|
<CARD:address-data>BEGIN:VCARD\
|
||||||
|
VERSION:3.0\
|
||||||
|
NICKNAME:MULTIGET1\
|
||||||
|
UID:1\
|
||||||
|
END:VCARD\
|
||||||
|
</CARD:address-data>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
<response>\
|
||||||
|
<href>/dav/addressbooks/default.vcf/2.vcf</href>\
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<getetag/>\
|
||||||
|
<CARD:address-data>BEGIN:VCARD\
|
||||||
|
VERSION:3.0\
|
||||||
|
NICKNAME:MULTIGET2\
|
||||||
|
UID:2\
|
||||||
|
END:VCARD\
|
||||||
|
</CARD:address-data>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
57
app/src/androidTest/robohydra/plugins/headdav.js
Normal file
57
app/src/androidTest/robohydra/plugins/headdav.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
var roboHydra = require("robohydra"),
|
||||||
|
roboHydraHeads = roboHydra.heads,
|
||||||
|
roboHydraHead = roboHydraHeads.RoboHydraHead;
|
||||||
|
|
||||||
|
RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({
|
||||||
|
name: 'WebDAV Server',
|
||||||
|
mandatoryProperties: [ 'path' ],
|
||||||
|
optionalProperties: [ 'handler' ],
|
||||||
|
|
||||||
|
parentPropBuilder: function() {
|
||||||
|
var myHandler = this.handler;
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
// default DAV behavior
|
||||||
|
res.headers['DAV'] = 'addressbook, calendar-access';
|
||||||
|
res.statusCode = 500;
|
||||||
|
|
||||||
|
// verify Accept header
|
||||||
|
var accept = req.headers['accept'];
|
||||||
|
if (req.method == "GET" && (accept == undefined || !accept.match(/text\/(calendar|vcard|xml)/)) ||
|
||||||
|
(req.method == "PROPFIND" || req.method == "REPORT") && (accept == undefined || accept != "text/xml"))
|
||||||
|
res.statusCode = 406;
|
||||||
|
|
||||||
|
// DAV operations that work on all URLs
|
||||||
|
else if (req.method == "OPTIONS") {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.headers['Allow'] = 'OPTIONS, PROPFIND, GET, PUT, DELETE, REPORT';
|
||||||
|
|
||||||
|
} else if (req.method == "PROPFIND" && req.rawBody.toString().match(/current-user-principal/)) {
|
||||||
|
res.statusCode = 207;
|
||||||
|
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||||
|
<multistatus xmlns="DAV:">\
|
||||||
|
<response>\
|
||||||
|
<href>' + req.url + '</href> \
|
||||||
|
<propstat>\
|
||||||
|
<prop>\
|
||||||
|
<current-user-principal>\
|
||||||
|
<href>/dav/principals/users/test</href>\
|
||||||
|
</current-user-principal>\
|
||||||
|
</prop>\
|
||||||
|
<status>HTTP/1.1 200 OK</status>\
|
||||||
|
</propstat>\
|
||||||
|
</response>\
|
||||||
|
</multistatus>\
|
||||||
|
');
|
||||||
|
|
||||||
|
} else if (typeof myHandler != 'undefined')
|
||||||
|
myHandler(req,res,next);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = RoboHydraHeadDAV;
|
57
app/src/androidTest/robohydra/plugins/redirect/index.js
Normal file
57
app/src/androidTest/robohydra/plugins/redirect/index.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
require('../simple');
|
||||||
|
|
||||||
|
var RoboHydraHead = require('robohydra').heads.RoboHydraHead;
|
||||||
|
|
||||||
|
exports.getBodyParts = function(conf) {
|
||||||
|
return {
|
||||||
|
heads: [
|
||||||
|
// well-known URIs
|
||||||
|
new SimpleResponseHead({
|
||||||
|
path: '/.well-known/caldav',
|
||||||
|
status: 302,
|
||||||
|
headers: { Location: '/dav/' }
|
||||||
|
}),
|
||||||
|
new SimpleResponseHead({
|
||||||
|
path: '/.well-known/carddav',
|
||||||
|
status: 302,
|
||||||
|
headers: { Location: '/dav/' }
|
||||||
|
}),
|
||||||
|
|
||||||
|
// generic redirections
|
||||||
|
new RoboHydraHead({
|
||||||
|
path: '/redirect/301',
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
res.statusCode = 301;
|
||||||
|
var location = req.queryParams['to'] || '/assets/test.random';
|
||||||
|
res.headers = {
|
||||||
|
Location: location
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new RoboHydraHead({
|
||||||
|
path: '/redirect/302',
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
res.statusCode = 302;
|
||||||
|
var location = req.queryParams['to'] || '/assets/test.random';
|
||||||
|
res.headers = {
|
||||||
|
Location: location
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// special redirections
|
||||||
|
new SimpleResponseHead({
|
||||||
|
path: '/redirect/relative',
|
||||||
|
status: 302,
|
||||||
|
headers: { Location: '/new/location' }
|
||||||
|
}),
|
||||||
|
new SimpleResponseHead({
|
||||||
|
path: '/redirect/without-location',
|
||||||
|
status: 302
|
||||||
|
})
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
28
app/src/androidTest/robohydra/plugins/simple.js
Normal file
28
app/src/androidTest/robohydra/plugins/simple.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
var roboHydra = require("robohydra"),
|
||||||
|
roboHydraHeads = roboHydra.heads,
|
||||||
|
roboHydraHead = roboHydraHeads.RoboHydraHead;
|
||||||
|
|
||||||
|
SimpleResponseHead = roboHydraHeads.roboHydraHeadType({
|
||||||
|
name: 'Simple HTTP Response',
|
||||||
|
mandatoryProperties: [ 'path', 'status' ],
|
||||||
|
optionalProperties: [ 'headers', 'body' ],
|
||||||
|
|
||||||
|
parentPropBuilder: function() {
|
||||||
|
var head = this;
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
handler: function(req,res,next) {
|
||||||
|
res.statusCode = head.status;
|
||||||
|
if (typeof head.headers != 'undefined')
|
||||||
|
res.headers = head.headers;
|
||||||
|
if (typeof head.body != 'undefined')
|
||||||
|
res.write(head.body);
|
||||||
|
else
|
||||||
|
res.write();
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SimpleResponseHead;
|
2
app/src/androidTest/robohydra/run.sh
Executable file
2
app/src/androidTest/robohydra/run.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
node_modules/robohydra/bin/robohydra.js davdroid.conf -I plugins
|
Loading…
Reference in a new issue