[headless] Add first integration test for ContactController

This commit is contained in:
Torsten Grote
2018-11-06 10:52:02 -03:00
parent a5c9e7c74d
commit d857338ad0
9 changed files with 283 additions and 33 deletions

View File

@@ -30,6 +30,9 @@ dependencies {
testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
testImplementation "io.mockk:mockk:1.8.12" testImplementation "io.mockk:mockk:1.8.12"
testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.skyscreamer:jsonassert:1.5.0"
testImplementation 'khttp:khttp:0.1.0'
kaptTest 'com.google.dagger:dagger-compiler:2.19'
} }
jar { jar {
@@ -48,6 +51,7 @@ jar {
idea { idea {
module { module {
sourceDirs += file('build/generated/source/kapt/main') sourceDirs += file('build/generated/source/kapt/main')
testSourceDirs += file('build/generated/source/kapt/test')
generatedSourceDirs += file('build/generated/source/kapt/main') generatedSourceDirs += file('build/generated/source/kapt/main')
} }
} }

View File

@@ -13,17 +13,22 @@ import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
interface BriarService {
fun start()
fun stop()
}
@Immutable @Immutable
@Singleton @Singleton
internal class BriarService internal class BriarServiceImpl
@Inject @Inject
constructor( constructor(
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val lifecycleManager: LifecycleManager, private val lifecycleManager: LifecycleManager,
private val passwordStrengthEstimator: PasswordStrengthEstimator private val passwordStrengthEstimator: PasswordStrengthEstimator
) { ) : BriarService {
fun start() { override fun start() {
if (!accountManager.accountExists()) { if (!accountManager.accountExists()) {
createAccount() createAccount()
} else { } else {
@@ -38,7 +43,7 @@ constructor(
lifecycleManager.startServices(dbKey) lifecycleManager.startServices(dbKey)
} }
fun stop() { override fun stop() {
lifecycleManager.stopServices() lifecycleManager.stopServices()
lifecycleManager.waitForShutdown() lifecycleManager.waitForShutdown()
} }

View File

@@ -56,6 +56,10 @@ import javax.net.SocketFactory
) )
internal class HeadlessModule(private val appDir: File) { internal class HeadlessModule(private val appDir: File) {
@Provides
@Singleton
internal fun provideBriarService(briarService: BriarServiceImpl): BriarService = briarService
@Provides @Provides
@Singleton @Singleton
internal fun provideDatabaseConfig(): DatabaseConfig { internal fun provideDatabaseConfig(): DatabaseConfig {
@@ -71,30 +75,21 @@ internal class HeadlessModule(private val appDir: File) {
locationUtils: LocationUtils, eventBus: EventBus, resourceProvider: ResourceProvider, locationUtils: LocationUtils, eventBus: EventBus, resourceProvider: ResourceProvider,
circumventionProvider: CircumventionProvider, batteryManager: BatteryManager, clock: Clock circumventionProvider: CircumventionProvider, batteryManager: BatteryManager, clock: Clock
): PluginConfig { ): PluginConfig {
val torDirectory = File(appDir, "tor") val duplex: List<DuplexPluginFactory> = if (isLinux() || isMac()) {
val duplex: List<DuplexPluginFactory> val torDirectory = File(appDir, "tor")
if (isLinux() || isMac()) {
val tor = UnixTorPluginFactory( val tor = UnixTorPluginFactory(
ioExecutor, networkManager, locationUtils, eventBus, torSocketFactory, ioExecutor, networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, circumventionProvider, batteryManager, clock, backoffFactory, resourceProvider, circumventionProvider, batteryManager, clock,
torDirectory torDirectory
) )
duplex = listOf(tor) listOf(tor)
} else { } else {
duplex = emptyList() emptyList()
} }
return object : PluginConfig { return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> { override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex
return duplex override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()
} override fun shouldPoll(): Boolean = true
override fun getSimplexFactories(): Collection<SimplexPluginFactory> {
return emptyList()
}
override fun shouldPoll(): Boolean {
return true
}
} }
} }
@@ -104,21 +99,14 @@ internal class HeadlessModule(private val appDir: File) {
return object : DevConfig { return object : DevConfig {
override fun getDevPublicKey(): PublicKey { override fun getDevPublicKey(): PublicKey {
try { try {
return crypto.messageKeyParser return crypto.messageKeyParser.parsePublicKey(fromHexString(DEV_PUBLIC_KEY_HEX))
.parsePublicKey(fromHexString(DEV_PUBLIC_KEY_HEX))
} catch (e: GeneralSecurityException) { } catch (e: GeneralSecurityException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
override fun getDevOnionAddress(): String { override fun getDevOnionAddress(): String = DEV_ONION_ADDRESS
return DEV_ONION_ADDRESS override fun getReportDir(): File = File(appDir, "reportDir")
}
override fun getReportDir(): File {
return File(appDir, "reportDir")
}
} }
} }

View File

@@ -41,7 +41,7 @@ constructor(
private val logger = getLogger(Router::javaClass.name) private val logger = getLogger(Router::javaClass.name)
private val stopped = AtomicBoolean(false) private val stopped = AtomicBoolean(false)
fun start(authToken: String, port: Int, debug: Boolean) { internal fun start(authToken: String, port: Int, debug: Boolean) : Javalin {
briarService.start() briarService.start()
getRuntime().addShutdownHook(Thread(this::stop)) getRuntime().addShutdownHook(Thread(this::stop))
@@ -104,7 +104,7 @@ constructor(
webSocketController.sessions.remove(session) webSocketController.sessions.remove(session)
} }
} }
app.start() return app.start()
} }
private fun serverStopped() { private fun serverStopped() {
@@ -112,7 +112,7 @@ constructor(
exit(1) exit(1)
} }
private fun stop() { internal fun stop() {
if (!stopped.getAndSet(true)) { if (!stopped.getAndSet(true)) {
briarService.stop() briarService.stop()
} }

View File

@@ -0,0 +1,27 @@
package org.briarproject.briar.headless
import dagger.Component
import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.BrambleCoreModule
import org.briarproject.bramble.account.AccountModule
import org.briarproject.bramble.test.TestSecureRandomModule
import org.briarproject.briar.BriarCoreEagerSingletons
import org.briarproject.briar.BriarCoreModule
import org.briarproject.briar.api.test.TestDataCreator
import javax.inject.Singleton
@Component(
modules = [
BrambleCoreModule::class,
BriarCoreModule::class,
TestSecureRandomModule::class,
AccountModule::class,
HeadlessTestModule::class
]
)
@Singleton
internal interface BriarHeadlessTestApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons {
fun getRouter(): Router
fun getTestDataCreator(): TestDataCreator
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.briar.headless
import org.briarproject.bramble.api.account.AccountManager
import org.briarproject.bramble.api.lifecycle.LifecycleManager
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
const val user = "user"
const val pass = "pass"
@Immutable
@Singleton
internal class BriarTestServiceImpl
@Inject
constructor(
private val accountManager: AccountManager,
private val lifecycleManager: LifecycleManager
) : BriarService {
override fun start() {
if (accountManager.accountExists()) {
accountManager.deleteAccount()
}
accountManager.createAccount(user, pass)
if (!accountManager.signIn(pass)) {
throw java.lang.AssertionError("Password invalid")
}
val dbKey = accountManager.databaseKey ?: throw AssertionError()
lifecycleManager.startServices(dbKey)
}
override fun stop() {
lifecycleManager.stopServices()
lifecycleManager.waitForShutdown()
}
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.briar.headless
import com.fasterxml.jackson.databind.ObjectMapper
import dagger.Module
import dagger.Provides
import org.briarproject.bramble.api.crypto.PublicKey
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory
import org.briarproject.bramble.api.reporting.DevConfig
import org.briarproject.bramble.network.JavaNetworkModule
import org.briarproject.bramble.plugin.tor.CircumventionModule
import org.briarproject.bramble.system.JavaSystemModule
import org.briarproject.briar.headless.blogs.HeadlessBlogModule
import org.briarproject.briar.headless.contact.HeadlessContactModule
import org.briarproject.briar.headless.event.HeadlessEventModule
import org.briarproject.briar.headless.forums.HeadlessForumModule
import org.briarproject.briar.headless.messaging.HeadlessMessagingModule
import java.io.File
import java.util.Collections.emptyList
import javax.inject.Singleton
@Module(
includes = [
JavaNetworkModule::class,
JavaSystemModule::class,
CircumventionModule::class,
HeadlessBlogModule::class,
HeadlessContactModule::class,
HeadlessEventModule::class,
HeadlessForumModule::class,
HeadlessMessagingModule::class
]
)
internal class HeadlessTestModule(private val appDir: File) {
@Provides
@Singleton
internal fun provideBriarService(briarService: BriarTestServiceImpl): BriarService =
briarService
@Provides
@Singleton
internal fun provideDatabaseConfig(): DatabaseConfig {
val dbDir = File(appDir, "db")
val keyDir = File(appDir, "key")
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
internal fun providePluginConfig(): PluginConfig {
return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList()
override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()
override fun shouldPoll(): Boolean = false
}
}
@Provides
@Singleton
internal fun provideDevConfig(): DevConfig = object : DevConfig {
override fun getDevPublicKey(): PublicKey = throw NotImplementedError()
override fun getDevOnionAddress(): String = throw NotImplementedError()
override fun getReportDir(): File = throw NotImplementedError()
}
@Provides
@Singleton
internal fun provideObjectMapper() = ObjectMapper()
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.briar.headless
import io.javalin.Javalin
import io.javalin.core.util.Header.AUTHORIZATION
import khttp.responses.Response
import org.briarproject.bramble.BrambleCoreModule
import org.briarproject.briar.BriarCoreModule
import org.briarproject.briar.api.test.TestDataCreator
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import java.io.File
const val port = 8000
const val url = "http://127.0.0.1:$port/v1"
const val token = "authToken"
@TestInstance(PER_CLASS)
abstract class IntegrationTest {
private val dataDir = File("tmp")
protected lateinit var api: Javalin
protected lateinit var testDataCreator: TestDataCreator
private lateinit var router: Router
@BeforeAll
fun setUp() {
val app = DaggerBriarHeadlessTestApp.builder()
.headlessTestModule(HeadlessTestModule(dataDir))
.build()
BrambleCoreModule.initEagerSingletons(app)
BriarCoreModule.initEagerSingletons(app)
router = app.getRouter()
testDataCreator = app.getTestDataCreator()
api = router.start(token, port, false)
}
@AfterAll
fun tearDown() {
router.stop()
dataDir.deleteRecursively()
}
protected fun get(url: String) : Response {
return khttp.get(url, getAuthTokenHeader(token))
}
protected fun getWithWrongToken(url: String) : Response {
return khttp.get(url, getAuthTokenHeader("wrongToken"))
}
protected fun delete(url: String) : Response {
return khttp.delete(url, getAuthTokenHeader(token))
}
protected fun deleteWithWrongToken(url: String) : Response {
return khttp.delete(url, getAuthTokenHeader("wrongToken"))
}
private fun getAuthTokenHeader(token: String) = mapOf(Pair(AUTHORIZATION, "Bearer $token"))
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.briar.headless.contact
import org.briarproject.briar.headless.IntegrationTest
import org.briarproject.briar.headless.url
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class ContactControllerIntegrationTest: IntegrationTest() {
@Test
fun `list of contacts need authentication token`() {
val response = getWithWrongToken("$url/contacts")
assertEquals(401, response.statusCode)
}
@Test
fun `returns list of contacts`() {
// retrieve empty list of contacts
var response = get("$url/contacts")
assertEquals(200, response.statusCode)
assertEquals(0, response.jsonArray.length())
// add one test contact
val testContactName= "testContactName"
testDataCreator.addContact(testContactName)
// retrieve list with one test contact
response = get("$url/contacts")
assertEquals(200, response.statusCode)
assertEquals(1, response.jsonArray.length())
val contact = response.jsonArray.getJSONObject(0)
val author = contact.getJSONObject("author")
assertEquals(testContactName, author.getString("name"))
}
@Test
fun `deleting contact need authentication token`() {
val response = deleteWithWrongToken("$url/contacts/1")
assertEquals(401, response.statusCode)
}
@Test
fun `deleting real and non-existing contact`() {
var response = delete("$url/contacts/1")
assertEquals(200, response.statusCode)
response = delete("$url/contacts/1")
assertEquals(404, response.statusCode)
}
}