diff --git a/core/src/main/java/de/qabel/core/index/models.kt b/core/src/main/java/de/qabel/core/index/models.kt index 660356c2..a7053ea1 100644 --- a/core/src/main/java/de/qabel/core/index/models.kt +++ b/core/src/main/java/de/qabel/core/index/models.kt @@ -19,7 +19,8 @@ import java.util.* data class IndexContact( val publicKey: QblECPublicKey, val dropUrl: DropURL, - val alias: String + val alias: String, + val matches: List = listOf() ) { fun toContact(): Contact { return Contact(alias, listOf(dropUrl), publicKey) @@ -40,7 +41,7 @@ data class UpdateIdentity( val keyPair: QblECKeyPair, val dropURL: DropURL, val alias: String, - val fields: List + val fields: List = listOf() ) { constructor(identity: Identity, fields: List) : @@ -92,12 +93,59 @@ enum class FieldType { PHONE, } + +data class Field( + val field: FieldType, + val value: String +) + + data class UpdateField( val action: UpdateAction, val field: FieldType, val value: String ) +enum class EntryStatusEnum { + /** + * This field entry was confirmed and is live; publicly visible. + */ + @SerializedName("confirmed") + CONFIRMED, + + /** + * Confirmation (by user) pending, not publicly visible. + */ + @SerializedName("unconfirmed") + UNCONFIRMED, + + /** + * Confirmation (by user) to delete publicly visible entry. Currently this does normally not happen as there + * is no way at the moment to craft such a request through any of the clients, which authorize + * deletions through encrypted requests instead. + */ + @SerializedName("deletion-pending") + DELETION_PENDING +} + +data class EntryStatus( + val status: EntryStatusEnum, + val field: FieldType, + val value: String +) + +data class IdentityStatus( + /** + * Current identity data as returned by server (drop URL, alias, public key). + */ + val identity: IndexContact?, + /** + * A list of field statuses for every confirmed or unconfirmed (pending) entry associated with the + * identity. + */ + val fieldStatus: List +) + /** * Result of an update request issued by [IndexServer.publishIdentity] or [IndexServer.unpublishIdentity]. */ @@ -115,18 +163,28 @@ private val IndexContactDeserializer = jsonDeserializer { */ val obj = it.json.obj if (!obj.contains("public_key") || !obj.contains("alias") || !obj.contains("drop_url")) { - throw IllegalArgumentException("missing key in identity") + throw IllegalArgumentException("missing key in contact") + } + val matches = if (obj.contains("matches") && obj["matches"].isJsonArray()) { + it.context.deserialize>(obj["matches"], Array::class.java).asList() + } else { + listOf() } - /* If a custom TypeAdapter is around, at least this level has to be spelled out, since we don't have access to - * the generic type adapter here. - */ IndexContact( publicKey = it.context.deserialize(obj["public_key"], QblECPublicKey::class.java), dropUrl = it.context.deserialize(obj["drop_url"], DropURL::class.java), - alias = obj["alias"].string + alias = obj["alias"].string, + matches = matches ) } +private val IndexContactSerializer = jsonSerializer { + it.context.serialize(mapOf( + Pair("public_key", it.src.publicKey), + Pair("drop_url", it.src.dropUrl), + Pair("alias", it.src.alias))) +} + /** * Return Gson instance with necessary TypeAdapters to serdes JSON according to the spec * http://qabel.github.io/docs/Qabel-Index/ @@ -135,6 +193,7 @@ internal fun createGson(): Gson { return GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(IndexContactDeserializer) + .registerTypeAdapter(IndexContactSerializer) .create() } diff --git a/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpoint.kt b/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpoint.kt new file mode 100644 index 00000000..c026034f --- /dev/null +++ b/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpoint.kt @@ -0,0 +1,9 @@ +package de.qabel.core.index.server + +import de.qabel.core.crypto.QblECPublicKey +import de.qabel.core.index.UpdateIdentity +import org.apache.http.client.methods.HttpUriRequest + +internal interface DeleteIdentityEndpoint : EndpointBase { + fun buildRequest(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): HttpUriRequest +} diff --git a/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpointImpl.kt b/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpointImpl.kt new file mode 100644 index 00000000..bd7370a6 --- /dev/null +++ b/core/src/main/java/de/qabel/core/index/server/DeleteIdentityEndpointImpl.kt @@ -0,0 +1,33 @@ +package de.qabel.core.index.server + +import com.google.gson.Gson +import de.qabel.core.crypto.QblECPublicKey +import de.qabel.core.index.* +import de.qabel.core.logging.QabelLog +import org.apache.http.StatusLine +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpUriRequest + + +internal class DeleteIdentityEndpointImpl( + val location: IndexHTTPLocation, + val gson: Gson = createGson() +) : DeleteIdentityEndpoint, QabelLog { + fun buildJsonRequest(identity: UpdateIdentity): String { + return gson.toJson(EncryptedApiRequest( + api = "delete-identity", + timestamp = System.currentTimeMillis() / 1000 + )) + } + + override fun buildRequest(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): HttpUriRequest { + val json = buildJsonRequest(identity) + val uri = location.getUriBuilderForEndpoint("delete-identity").build() + val request = HttpPost(uri) + encryptJsonIntoRequest(json, identity.keyPair, serverPublicKey, request) + return request + } + + override fun parseResponse(jsonString: String, statusLine: StatusLine) { + } +} diff --git a/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpoint.kt b/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpoint.kt new file mode 100644 index 00000000..4b3ae20e --- /dev/null +++ b/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpoint.kt @@ -0,0 +1,10 @@ +package de.qabel.core.index.server + +import de.qabel.core.crypto.QblECPublicKey +import de.qabel.core.index.IdentityStatus +import de.qabel.core.index.UpdateIdentity +import org.apache.http.client.methods.HttpUriRequest + +internal interface IdentityStatusEndpoint : EndpointBase { + fun buildRequest(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): HttpUriRequest +} diff --git a/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpointImpl.kt b/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpointImpl.kt new file mode 100644 index 00000000..bd3f236e --- /dev/null +++ b/core/src/main/java/de/qabel/core/index/server/IdentityStatusEndpointImpl.kt @@ -0,0 +1,44 @@ +package de.qabel.core.index.server + +import com.google.gson.Gson +import de.qabel.core.crypto.QblECPublicKey +import de.qabel.core.index.* +import de.qabel.core.logging.QabelLog +import org.apache.http.StatusLine +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpUriRequest + + +internal class IdentityStatusEndpointImpl( + val location: IndexHTTPLocation, + val gson: Gson = createGson() +) : IdentityStatusEndpoint, QabelLog { + fun buildJsonRequest(identity: UpdateIdentity): String { + return gson.toJson(EncryptedApiRequest( + api = "status", + timestamp = System.currentTimeMillis() / 1000 + )) + } + + override fun buildRequest(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): HttpUriRequest { + val json = buildJsonRequest(identity) + val uri = location.getUriBuilderForEndpoint("status").build() + val request = HttpPost(uri) + encryptJsonIntoRequest(json, identity.keyPair, serverPublicKey, request) + return request + } + + private data class IdentityStatusResponse( + val identity: IndexContact, + val entries: List + ) + + override fun parseResponse(jsonString: String, statusLine: StatusLine): IdentityStatus { + debug("Received identity status response: ${jsonString}") + val response = gson.fromJson(jsonString, IdentityStatusResponse::class.java) + return IdentityStatus( + identity = response.identity, + fieldStatus = response.entries + ) + } +} diff --git a/core/src/main/java/de/qabel/core/index/server/IndexHTTP.kt b/core/src/main/java/de/qabel/core/index/server/IndexHTTP.kt index f90c3cad..89dedb71 100644 --- a/core/src/main/java/de/qabel/core/index/server/IndexHTTP.kt +++ b/core/src/main/java/de/qabel/core/index/server/IndexHTTP.kt @@ -13,45 +13,49 @@ internal constructor ( private val key: ServerPublicKeyEndpoint = ServerPublicKeyEndpointImpl(location), private val search: SearchEndpoint = SearchEndpointImpl(location), private val update: UpdateEndpoint = UpdateEndpointImpl(location), + private val status: IdentityStatusEndpoint = IdentityStatusEndpointImpl(location), + private val deleteIdentity: DeleteIdentityEndpoint = DeleteIdentityEndpointImpl(location), private val verificationCode: VerificationCodeEndpoint = VerificationCodeEndpointImpl(location) ): IndexServer { constructor (location: IndexHTTPLocation, httpClient: CloseableHttpClient) : this(location, httpClient, ServerPublicKeyEndpointImpl(location)) - override fun search(attributes: Map): List { - val request = search.buildRequest(attributes) + override fun search(manyAttributes: List): List { + val request = search.buildRequest(manyAttributes) val response = httpClient.execute(request) return search.handleResponse(response) } - override fun updateIdentity(identity: UpdateIdentity): UpdateResult { - return updateIdentityWithRetries(identity) + override fun search(attributes: Map): List { + val request = search.buildRequest(attributes.map { + Field(it.key, it.value) + }) + val response = httpClient.execute(request) + return search.handleResponse(response) } - fun updateIdentityWithRetries(identity: UpdateIdentity, retries: Int = 0): UpdateResult { - val serverPublicKey = retrieveServerPublicKey() - try { - return updateIdentity(identity, serverPublicKey) - } catch (e: APIError) { - e.retries = retries - if (e.code == HttpStatus.SC_BAD_REQUEST && retries < 2) { - // a bad request / 400 may be caused by an outdated server public key, retry two times (total tries = 3) - return updateIdentityWithRetries(identity, retries + 1) - } - throw e - } + override fun identityStatus(identity: UpdateIdentity): IdentityStatus { + return requestWithRetries({ serverPublicKey -> + val request = status.buildRequest(identity, serverPublicKey) + val response = httpClient.execute(request) + status.handleResponse(response) + }) } - internal fun updateIdentity(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): UpdateResult { - val request = update.buildRequest(identity, serverPublicKey) - val response = httpClient.execute(request) - return update.handleResponse(response) + override fun deleteIdentity(identity: UpdateIdentity) { + return requestWithRetries({ serverPublicKey -> + val request = deleteIdentity.buildRequest(identity, serverPublicKey) + val response = httpClient.execute(request) + deleteIdentity.handleResponse(response) + }) } - internal fun retrieveServerPublicKey(): QblECPublicKey { - val request = key.buildRequest() - val response = httpClient.execute(request) - return key.handleResponse(response) + override fun updateIdentity(identity: UpdateIdentity): UpdateResult { + return requestWithRetries({ serverPublicKey -> + val request = update.buildRequest(identity, serverPublicKey) + val response = httpClient.execute(request) + update.handleResponse(response) + }) } override fun confirmVerificationCode(code: String) { @@ -67,4 +71,24 @@ internal constructor ( val response = httpClient.execute(request) verificationCode.handleResponse(response) } + + fun requestWithRetries(body: (serverPublicKey: QblECPublicKey) -> T, retries: Int = 0): T { + val serverPublicKey = retrieveServerPublicKey() + try { + return body(serverPublicKey) + } catch (e: APIError) { + e.retries = retries + if (e.code == HttpStatus.SC_BAD_REQUEST && retries < 2) { + // a bad request / 400 may be caused by an outdated server public key, retry two times (total tries = 3) + return requestWithRetries(body, retries + 1) + } + throw e + } + } + + internal fun retrieveServerPublicKey(): QblECPublicKey { + val request = key.buildRequest() + val response = httpClient.execute(request) + return key.handleResponse(response) + } } diff --git a/core/src/main/java/de/qabel/core/index/server/IndexServer.kt b/core/src/main/java/de/qabel/core/index/server/IndexServer.kt index 8ef88206..a0d87043 100644 --- a/core/src/main/java/de/qabel/core/index/server/IndexServer.kt +++ b/core/src/main/java/de/qabel/core/index/server/IndexServer.kt @@ -11,6 +11,15 @@ import java.io.IOException * [APIError] and [MalformedResponseException]. */ interface IndexServer { + /** + * Search for many attributes in one request. + * + * Returns a list of [IndexContact] instances where each [IndexContact.matches] list is a list of attributes + * from [manyAttributes] that matched it. If nothing is found, returns an empty list. + */ + @Throws(IOException::class) + fun search(manyAttributes: List): List + /** * SearchEndpoint for identities on the index server. * @@ -37,6 +46,18 @@ interface IndexServer { return search(mapOf(Pair(FieldType.PHONE, phone))) } + /** + * Fetch the [IdentityStatus] of [identity]. [identity.fields] are ignored. + */ + @Throws(IOException::class) + fun identityStatus(identity: UpdateIdentity): IdentityStatus + + /** + * Delete all data related to [identity] from the index. [identity.fields] are ignored. + */ + @Throws(IOException::class) + fun deleteIdentity(identity: UpdateIdentity) + /** * UpdateEndpoint published data of [identity] on the index. * @@ -49,6 +70,9 @@ interface IndexServer { * 2. [UpdateField] with the new email address and [UpdateAction.CREATE] * * As soon as the user confirms the new email address the old email address will be replaced seamlessly. + * + * The drop URL and alias on the server are updated with the values of [identity] (to only update these, + * leave [identity.fields] empty). */ @Throws(IOException::class) fun updateIdentity(identity: UpdateIdentity): UpdateResult diff --git a/core/src/main/java/de/qabel/core/index/server/SearchEndpoint.kt b/core/src/main/java/de/qabel/core/index/server/SearchEndpoint.kt index 2369a3d9..49156c7c 100644 --- a/core/src/main/java/de/qabel/core/index/server/SearchEndpoint.kt +++ b/core/src/main/java/de/qabel/core/index/server/SearchEndpoint.kt @@ -1,9 +1,9 @@ package de.qabel.core.index.server -import de.qabel.core.index.FieldType +import de.qabel.core.index.Field import de.qabel.core.index.IndexContact import org.apache.http.client.methods.HttpUriRequest internal interface SearchEndpoint : EndpointBase> { - fun buildRequest(attributes: Map): HttpUriRequest + fun buildRequest(manyAttributes: List): HttpUriRequest } diff --git a/core/src/main/java/de/qabel/core/index/server/SearchEndpointImpl.kt b/core/src/main/java/de/qabel/core/index/server/SearchEndpointImpl.kt index 526982eb..5e359928 100644 --- a/core/src/main/java/de/qabel/core/index/server/SearchEndpointImpl.kt +++ b/core/src/main/java/de/qabel/core/index/server/SearchEndpointImpl.kt @@ -3,13 +3,14 @@ package de.qabel.core.index.server import com.github.salomonbrys.kotson.* import com.google.gson.* import de.qabel.core.exceptions.QblDropInvalidURL -import de.qabel.core.index.FieldType -import de.qabel.core.index.IndexContact -import de.qabel.core.index.MalformedResponseException -import de.qabel.core.index.createGson +import de.qabel.core.index.* +import de.qabel.core.logging.QabelLog import org.apache.http.StatusLine import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPost import org.apache.http.client.methods.HttpUriRequest +import org.apache.http.entity.ByteArrayEntity +import org.apache.http.entity.StringEntity import org.slf4j.Logger import org.slf4j.LoggerFactory import java.net.URISyntaxException @@ -19,21 +20,25 @@ import java.util.* internal class SearchEndpointImpl( private val location: IndexHTTPLocation, private val gson: Gson = createGson() -): SearchEndpoint { +): SearchEndpoint, QabelLog { private val logger: Logger by lazy { LoggerFactory.getLogger(SearchEndpointImpl::class.java) } - override fun buildRequest(attributes: Map): HttpUriRequest { - if (attributes.size == 0) { + private data class SearchRequest( + val query: List + ) + + override fun buildRequest(manyAttributes: List): HttpUriRequest { + if (manyAttributes.size == 0) { throw IllegalArgumentException("Need at least one attribute to search for.") } - val uriBuilder = location.getUriBuilderForEndpoint("search") - // query parameters are part of the URI(Builder) - for ((type, value) in attributes) { - uriBuilder.addParameter(type.name.toLowerCase(), value) - } - return HttpGet(uriBuilder.build()) + val uri = location.getUriBuilderForEndpoint("search").build() + val json = gson.toJson(SearchRequest(manyAttributes)) + val request = HttpPost(uri) + request.addHeader("Content-Type", "application/json") + request.entity = StringEntity(json) + return request } override fun parseResponse(jsonString: String, statusLine: StatusLine): List { @@ -48,10 +53,10 @@ internal class SearchEndpointImpl( else -> throw e } } - val identities = ArrayList() + val contacts = ArrayList() for (serializedIdentity in root) { try { - identities += gson.fromJson(serializedIdentity) + contacts += gson.fromJson(serializedIdentity) } catch (e: Throwable) { when (e) { is JsonSyntaxException, @@ -62,6 +67,7 @@ internal class SearchEndpointImpl( } } } - return identities + logger.debug("parsed response, returning ${contacts.size} out of ${root.size()} contacts") + return contacts } } diff --git a/core/src/main/java/de/qabel/core/index/server/UpdateEndpointImpl.kt b/core/src/main/java/de/qabel/core/index/server/UpdateEndpointImpl.kt index 3a1881a7..70edcd6e 100644 --- a/core/src/main/java/de/qabel/core/index/server/UpdateEndpointImpl.kt +++ b/core/src/main/java/de/qabel/core/index/server/UpdateEndpointImpl.kt @@ -29,19 +29,11 @@ internal class UpdateEndpointImpl( return gson.toJson(updateRequest) } - fun encryptJson(json: String, senderKeyPair: QblECKeyPair, serverPublicKey: QblECPublicKey): ByteArray { - val box = CryptoUtils().createBox(senderKeyPair, serverPublicKey, json.toByteArray(), 0) - return box - } - override fun buildRequest(identity: UpdateIdentity, serverPublicKey: QblECPublicKey): HttpUriRequest { val json = buildJsonUpdateRequest(identity) - val encryptedJson = encryptJson(json, identity.keyPair, serverPublicKey) - val uri = location.getUriBuilderForEndpoint("update").build() val request = HttpPut(uri) - request.addHeader("Content-Type", "application/vnd.qabel.noisebox+json") - request.entity = ByteArrayEntity(encryptedJson) + encryptJsonIntoRequest(json, identity.keyPair, serverPublicKey, request) return request } diff --git a/core/src/main/java/de/qabel/core/index/server/utils.kt b/core/src/main/java/de/qabel/core/index/server/utils.kt new file mode 100644 index 00000000..264d6f47 --- /dev/null +++ b/core/src/main/java/de/qabel/core/index/server/utils.kt @@ -0,0 +1,30 @@ +package de.qabel.core.index.server + +import de.qabel.core.crypto.CryptoUtils +import de.qabel.core.crypto.QblECKeyPair +import de.qabel.core.crypto.QblECPublicKey +import de.qabel.core.index.IndexContact +import de.qabel.core.index.UpdateField +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase +import org.apache.http.entity.ByteArrayEntity + +internal fun encryptJson(json: String, senderKeyPair: QblECKeyPair, serverPublicKey: QblECPublicKey): ByteArray { + val box = CryptoUtils().createBox(senderKeyPair, serverPublicKey, json.toByteArray(), 0) + return box +} + + +internal fun encryptJsonIntoRequest(json: String, + senderKeyPair: QblECKeyPair, + serverPublicKey: QblECPublicKey, + request: HttpEntityEnclosingRequestBase) { + val encryptedJson = encryptJson(json, senderKeyPair, serverPublicKey) + request.addHeader("Content-Type", "application/vnd.qabel.noisebox+json") + request.entity = ByteArrayEntity(encryptedJson) +} + + +internal data class EncryptedApiRequest( + val api: String, + val timestamp: Long +) diff --git a/core/src/test/java/de/qabel/core/index/server/IndexHTTPTest.kt b/core/src/test/java/de/qabel/core/index/server/IndexHTTPTest.kt index b94bfdf8..73f6e376 100644 --- a/core/src/test/java/de/qabel/core/index/server/IndexHTTPTest.kt +++ b/core/src/test/java/de/qabel/core/index/server/IndexHTTPTest.kt @@ -9,11 +9,10 @@ import de.qabel.core.index.* import org.apache.http.StatusLine import org.apache.http.client.methods.HttpUriRequest import org.junit.Assert.assertEquals -import org.junit.Ignore import org.junit.Test import java.util.* -@Ignore + class IndexHTTPTest { private val server = IndexHTTPLocation(TestServer.INDEX) private val index = IndexHTTP(server) @@ -51,7 +50,7 @@ class IndexHTTPTest { val index = IndexHTTP(server, key = outdatedKey) val testParts = UpdateTestParts(index) testParts.publishTest() - assertEquals(2, outdatedKey.numCalls) + assertEquals(3, outdatedKey.numCalls) outdatedKey.numCalls = 0 testParts.unpublishTest() assertEquals(2, outdatedKey.numCalls) @@ -69,17 +68,62 @@ class IndexHTTPTest { assertEquals(400, exception.code) } + @Test + fun testUpdateDropURL() { + val testParts = UpdateTestParts(index) + testParts.publishTest() + + val updatedIdentity = testParts.identity.copy(dropURL = DropURL("http://example.net/somewhere/abcdefghijklmnopqrstuvwxyzabcdefghijklmonop")) + val updateResult = index.updateIdentity(updatedIdentity) + assertEquals(UpdateResult.ACCEPTED_IMMEDIATE, updateResult) + + val search1Result = index.search(mapOf(Pair(FieldType.EMAIL, testParts.mail))) + assertEquals(1, search1Result.size) + val foundPublicIdentity = search1Result[0] + assertEquals(updatedIdentity.dropURL, foundPublicIdentity.dropUrl) + assertEquals(updatedIdentity.alias, foundPublicIdentity.alias) + } + + @Test + fun testUpdateAlias() { + val testParts = UpdateTestParts(index) + testParts.publishTest() + + val updatedIdentity = testParts.identity.copy(alias = "1234") + val updateResult = index.updateIdentity(updatedIdentity) + assertEquals(UpdateResult.ACCEPTED_IMMEDIATE, updateResult) + + val search1Result = index.search(mapOf(Pair(FieldType.EMAIL, testParts.mail))) + assertEquals(1, search1Result.size) + val foundPublicIdentity = search1Result[0] + assertEquals(updatedIdentity.dropURL, foundPublicIdentity.dropUrl) + assertEquals(updatedIdentity.alias, foundPublicIdentity.alias) + } + + @Test + fun testDeleteIdentity() { + val testParts = UpdateTestParts(index) + testParts.publishTest() + + val changedIdentity = testParts.identity.copy(alias = "1234", fields = listOf()) + index.deleteIdentity(changedIdentity) // only the key matters + + val search1Result = index.search(mapOf(Pair(FieldType.EMAIL, testParts.mail))) + assertEquals(0, search1Result.size) + + val status = index.identityStatus(testParts.identity) + assertEquals(null, status.identity) + } + private class UpdateTestParts(private val index: IndexHTTP) { - private val mail1 = getRandomMail() - private val mail2 = getRandomMail() + val mail = getRandomMail() val identity = UpdateIdentity( keyPair = QblECKeyPair(), dropURL = DropURL("http://example.net/somewhere/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopo"), alias = "Major Anya, Unicode: 裸共產主義", fields = listOf( - UpdateField(UpdateAction.CREATE, FieldType.EMAIL, mail1), - UpdateField(UpdateAction.CREATE, FieldType.EMAIL, mail2) + UpdateField(UpdateAction.CREATE, FieldType.EMAIL, mail) ) ) @@ -90,22 +134,28 @@ class IndexHTTPTest { */ assertEquals(UpdateResult.ACCEPTED_IMMEDIATE, updateResult) - /* Will be found with both mails */ - searchForMailAndAssertOurs(mail1) - searchForMailAndAssertOurs(mail2) + /* Will be found */ + searchForMailAndAssertOurs(mail) + + val status = index.identityStatus(identity) + assertIdentityEquals(identity, status.identity!!) + assertEquals(1, status.fieldStatus.size) + val field = status.fieldStatus[0] + assertEquals(EntryStatusEnum.CONFIRMED, field.status) + assertEquals(FieldType.EMAIL, field.field) + assertEquals(mail, field.value) } fun unpublishTest() { val identity = identity.copy( fields = listOf( - UpdateField(UpdateAction.DELETE, FieldType.EMAIL, mail2) + UpdateField(UpdateAction.DELETE, FieldType.EMAIL, mail) ) ) assertEquals(UpdateResult.ACCEPTED_IMMEDIATE, index.updateIdentity(identity)) - searchForMailAndAssertOurs(mail1) // still found - assertEquals(0, index.search(mapOf(Pair(FieldType.EMAIL, mail2))).size) // gone + assertEquals(0, index.search(mapOf(Pair(FieldType.EMAIL, mail))).size) // gone } fun assertIdentityEquals(identity: UpdateIdentity, indexContact: IndexContact) { diff --git a/core/src/test/java/de/qabel/core/index/server/SearchEndpointTest.kt b/core/src/test/java/de/qabel/core/index/server/SearchEndpointTest.kt index 43b1d9cc..fafa6b81 100644 --- a/core/src/test/java/de/qabel/core/index/server/SearchEndpointTest.kt +++ b/core/src/test/java/de/qabel/core/index/server/SearchEndpointTest.kt @@ -2,10 +2,8 @@ package de.qabel.core.index.server import de.qabel.core.TestServer import de.qabel.core.extensions.assertThrows -import de.qabel.core.index.FieldType -import de.qabel.core.index.MalformedResponseException -import de.qabel.core.index.createGson -import de.qabel.core.index.dummyStatusLine +import de.qabel.core.index.* +import org.apache.http.client.methods.HttpPost import org.junit.Assert.assertEquals import org.junit.Test @@ -15,14 +13,19 @@ class SearchEndpointTest { @Test(expected = IllegalArgumentException::class) fun testBuildSearchRequestEmpty() { - search.buildRequest(mapOf()) + search.buildRequest(listOf()) } @Test fun testBuildSearchRequestSingle() { - val attributes = mapOf(Pair(FieldType.EMAIL, "test@example.net")) + val attributes = listOf(Field(FieldType.EMAIL, "test@example.net")) val request = search.buildRequest(attributes) - assertEquals("email=test@example.net", request.uri.query) + assertEquals(null, request.uri.query) + assertEquals("POST", request.method) + assertEquals("application/json", request.getFirstHeader("Content-Type").value) + val postRequest = request as HttpPost + val json = postRequest.entity.content.reader(Charsets.UTF_8).readText() + assertEquals("""{"query":[{"field":"email","value":"test@example.net"}]}""", json) } @Test @@ -101,7 +104,8 @@ class SearchEndpointTest { {"identities": [{ "public_key": "0f82b0018d1140a37b9cf3a4570bbdd415c8bbbcfc7efe7ef8aa912ce3760520", "drop_url": "http://example.net/somewhere/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopo", - "alias": "1234" + "alias": "1234", + "matches": [] }]} """ val result = search.parseResponse(json, dummyStatusLine()) @@ -120,7 +124,8 @@ class SearchEndpointTest { "public_key": "0f82b0018d1140a37b9cf3a4570bbdd415c8bbbcfc7efe7ef8aa912ce3760520", "drop_url": "http://example.net/somewhere/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopo", "alias": "1234", - "field_from_the_future": "I'm back" + "field_from_the_future": "I'm back", + "matches": [] }]} """ val result = search.parseResponse(json, dummyStatusLine())