fixs and move files

This commit is contained in:
2019-08-03 00:57:00 +02:00
parent 63a50dcb92
commit 7c3028eca2
60 changed files with 171 additions and 105 deletions

View File

@@ -47,9 +47,3 @@ dependencies {
testImplementation("io.cucumber:cucumber-java8:4.3.1") testImplementation("io.cucumber:cucumber-java8:4.3.1")
testImplementation("io.cucumber:cucumber-junit:4.3.1") testImplementation("io.cucumber:cucumber-junit:4.3.1")
} }
kotlin.sourceSets["main"].kotlin.srcDirs("src")
kotlin.sourceSets["test"].kotlin.srcDirs("test")
sourceSets["main"].resources.srcDirs("resources")
sourceSets["test"].resources.srcDirs("testresources")

View File

@@ -1,18 +0,0 @@
create or replace procedure insert_user(inout resource json) language plpgsql as
$$
declare
new_id uuid;
begin
insert into "user" (username, password, blocked_at)
select
username,
crypt(resource->>'plain_password', gen_salt('bf', 8)),
case when blocked_at is not null then now() else null end
from json_populate_record(null::"user", resource)
returning id into new_id;
select find_user_by_id(new_id) into resource;
end;
$$;
-- drop procedure if exists insert_user(inout json);

View File

@@ -1,2 +0,0 @@
-- User
drop view if exists user_lite;

View File

@@ -1,5 +0,0 @@
-- User
create or replace view user_lite as
select u.id, u.created_at, u.blocked_at, u.username
from "user" u
where u.blocked_at is null;

View File

@@ -1,26 +0,0 @@
package fr.dcproject
import fr.postgresjson.connexion.Requester
import io.ktor.util.KtorExperimentalAPI
import org.koin.dsl.module
import java.io.File
import fr.dcproject.repository.Article as ArticleRepository
val config = Config()
@KtorExperimentalAPI
val Module = module {
single { config }
single { Requester.RequesterFactory(
host = config.host,
database = config.database,
username = config.username,
password = config.password,
port = config.port,
functionsDirectory = File(this::class.java.getResource("/sql/functions").toURI())
).createRequester() }
single { ArticleRepository(get<Requester>()) }
}

View File

@@ -52,8 +52,8 @@ fun Application.module() {
convert<Article> { convert<Article> {
decode { values, _ -> decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) } val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to Article") ?: throw InternalError("Cannot convert $values to UUID")
get<RepositoryArticle>().findById(id) get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
} }
} }
} }

View File

@@ -1,14 +1,16 @@
package fr.dcproject package fr.dcproject
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import java.io.File
class Config { class Config {
private var config = ConfigFactory.load() private var config = ConfigFactory.load()
val sqlFiles = File(this::class.java.getResource("/sql").toURI())
val envName: String = config.getString("app.envName") val envName: String = config.getString("app.envName")
val host: String = config.getString("db.host") val host: String = config.getString("db.host")
val database: String = config.getString("db.database") var database: String = config.getString("db.database")
val username: String = config.getString("db.username") var username: String = config.getString("db.username")
val password: String = config.getString("db.password") var password: String = config.getString("db.password")
val port: Int = config.getInt("db.port") val port: Int = config.getInt("db.port")
} }

View File

@@ -0,0 +1,26 @@
package fr.dcproject
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations
import io.ktor.util.KtorExperimentalAPI
import org.koin.dsl.module
import fr.dcproject.repository.Article as ArticleRepository
val config = Config()
@KtorExperimentalAPI
val Module = module {
single { config }
single { Connection(host = config.host, port = config.port, database = config.database, username = config.username, password = config.password) }
single { Requester.RequesterFactory(
connection = get(),
functionsDirectory = config.sqlFiles.resolve("functions")
).createRequester() }
single { ArticleRepository(get()) }
single { Migrations(connection = get(), directory = config.sqlFiles) }
}

View File

@@ -3,22 +3,23 @@ package fr.dcproject.entity
import fr.postgresjson.entity.EntityCreatedAt import fr.postgresjson.entity.EntityCreatedAt
import fr.postgresjson.entity.EntityCreatedAtImp import fr.postgresjson.entity.EntityCreatedAtImp
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import org.joda.time.DateTime
import java.util.* import java.util.*
class Citizen( class Citizen(
id: UUID?, id: UUID = UUID.randomUUID(),
var name: Name?, var name: Name?,
var birthday: String?, var birthday: DateTime?,
var userId: String?, var userId: String? = null,
var voteAnnonymous: Boolean?, var voteAnnonymous: Boolean? = null,
var followAnnonymous: Boolean?, var followAnnonymous: Boolean? = null,
var user: User? var user: User?
) : UuidEntity(id), ) : UuidEntity(id),
EntityCreatedAt by EntityCreatedAtImp() { EntityCreatedAt by EntityCreatedAtImp() {
data class Name( data class Name(
var civility: String?, var firstName: String?,
var lastName: String?, var lastName: String?,
var firstName: String? var civility: String? = null
) )
} }

View File

@@ -5,11 +5,10 @@ import org.joda.time.DateTime
import java.util.* import java.util.*
class User( class User(
id: UUID?, id: UUID? = UUID.randomUUID(),
var username: String?, var username: String?,
var blockedAt: DateTime?, var blockedAt: DateTime? = null,
override var createdAt: DateTime?, var plainPassword: String?
override var updatedAt: DateTime?
) : UuidEntity(id), ) : UuidEntity(id),
EntityCreatedAt by EntityCreatedAtImp(), EntityCreatedAt by EntityCreatedAtImp(),
EntityUpdatedAt by EntityUpdatedAtImp() EntityUpdatedAt by EntityUpdatedAtImp()

View File

@@ -1,4 +1,4 @@
create or replace procedure upsert_citizen(inout resource json) create or replace function upsert_citizen(inout resource json)
language plpgsql as language plpgsql as
$$ $$
declare declare
@@ -25,4 +25,4 @@ begin
end; end;
$$; $$;
-- drop procedure if exists insert_user(inout json); -- drop function if exists upsert_citizen(inout json);

View File

@@ -0,0 +1,19 @@
create or replace function insert_user(inout resource json) language plpgsql as
$$
declare
new_id uuid;
begin
insert into "user" (id, username, password, blocked_at)
select
coalesce(t.id, uuid_generate_v4()),
t.username,
crypt(resource->>'plain_password', gen_salt('bf', 8)),
case when t.blocked_at is not null then now() else null end
from json_populate_record(null::"user", resource) t
returning id into new_id;
select find_user_by_id(new_id) into resource;
end;
$$;
-- drop function if exists insert_user(inout json);

View File

@@ -1,6 +1,4 @@
-- Users -- Users
create extension if not exists pgcrypto;
create table "user" create table "user"
( (
id uuid default uuid_generate_v4() not null primary key, id uuid default uuid_generate_v4() not null primary key,

View File

@@ -9,7 +9,7 @@ declare
selected_article json; selected_article json;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
_user_id := created_user->>'id'; _user_id := created_user->>'id';
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text); assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);

View File

@@ -8,7 +8,7 @@ declare
selected_citizen json; selected_citizen json;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
_user_id := created_user->>'id'; _user_id := created_user->>'id';
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text); assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);

View File

@@ -28,8 +28,8 @@ declare
_comment_id uuid; _comment_id uuid;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
call insert_user(created_user2); select insert_user(created_user2) into created_user2;
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
-- insert new citizen for context -- insert new citizen for context

View File

@@ -42,7 +42,7 @@ declare
$json$; $json$;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
_user_id := created_user->>'id'; _user_id := created_user->>'id';
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', _user_id::text), true)::json;
assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text); assert created_citizen#>>'{user, id}' = _user_id::text, format('userId in citizen must be the same as user, %s = %s', created_citizen#>>'{user, id}', _user_id::text);

View File

@@ -25,8 +25,8 @@ declare
$json$; $json$;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
call insert_user(created_user2); select insert_user(created_user2) into created_user2;
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
created_citizen2 := jsonb_set(created_citizen2::jsonb, '{user}'::text[], jsonb_build_object('id', created_user2->>'id'), true)::json; created_citizen2 := jsonb_set(created_citizen2::jsonb, '{user}'::text[], jsonb_build_object('id', created_user2->>'id'), true)::json;

View File

@@ -6,7 +6,7 @@ declare
exist_user json; exist_user json;
begin begin
-- Insert user and check if username and password is correct -- Insert user and check if username and password is correct
call insert_user(created_user); select insert_user(created_user) into created_user;
assert created_user->>'username' = 'george', 'username must be george'; assert created_user->>'username' = 'george', 'username must be george';
assert created_user->>'password' is null, 'password must not be returned'; assert created_user->>'password' is null, 'password must not be returned';

View File

@@ -26,7 +26,7 @@ declare
$json$; $json$;
begin begin
-- insert user for context -- insert user for context
call insert_user(created_user); select insert_user(created_user) into created_user;
created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json; created_citizen := jsonb_set(created_citizen::jsonb, '{user}'::text[], jsonb_build_object('id', created_user->>'id'), true)::json;
-- insert new citizen for context -- insert new citizen for context

View File

@@ -1,17 +1,51 @@
package fr.dcproject package fr.dcproject
import fr.postgresjson.migration.Migrations
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.handleRequest import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.setBody import io.ktor.server.testing.setBody
import io.ktor.server.testing.withTestApplication import io.ktor.server.testing.withTestApplication
import kotlin.test.Test import io.ktor.util.KtorExperimentalAPI
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.test.KoinTest
import org.koin.test.inject
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class ArticleRouteTest { @KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class ArticleRouteTest: KoinTest {
private val migrations: Migrations by inject()
@BeforeEach
fun beforeAll() {
startKoin {
modules(Module)
config.database = "test"
config.username = "test"
config.password = "test"
}
migrations.run()
}
@AfterEach
fun afterAll() {
migrations.forceAllDown()
stopKoin()
}
private val article: String = """{ private val article: String = """{
"id" : "8e8dd0aa-2b2b-41e1-bff5-ea613c988774", "id" : "8e8dd0aa-2b2b-41e1-bff5-ea613c988774",
"version_id" : "e3ec9ea8-87ac-46ac-8321-8f2bc8c687bc", "version_id" : "e3ec9ea8-87ac-46ac-8321-8f2bc8c687bc",
@@ -39,7 +73,7 @@ class ArticleRouteTest {
}""" }"""
@Test @Test
fun testRoot() { fun testRoute() {
withTestApplication({ module() }) { withTestApplication({ module() }) {
handleRequest(HttpMethod.Get, "/articles").apply { handleRequest(HttpMethod.Get, "/articles").apply {
assertEquals(HttpStatusCode.OK, response.status()) assertEquals(HttpStatusCode.OK, response.status())

View File

@@ -3,22 +3,32 @@ import cucumber.api.Scenario
import cucumber.api.java8.En import cucumber.api.java8.En
import cucumber.api.junit.Cucumber import cucumber.api.junit.Cucumber
import feature.Context import feature.Context
import fr.dcproject.config
import fr.postgresjson.migration.Migrations
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.createTestEnvironment import io.ktor.server.testing.createTestEnvironment
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.inject
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import feature.Context.Companion.current as contextCurrent import feature.Context.Companion.current as contextCurrent
@RunWith(Cucumber::class) @RunWith(Cucumber::class)
@CucumberOptions(plugin = ["pretty"]) @CucumberOptions(plugin = ["pretty"])
class RunCucumberTest: En { class RunCucumberTest: En, KoinTest {
private val migrations: Migrations by inject()
init { init {
Before(-1) { scenario: Scenario -> Before(-1) { scenario: Scenario ->
// config.database = "dc-projectg-test" config.database = "test"
config.username = "test"
config.password = "test"
contextCurrent = Context(TestApplicationEngine(createTestEnvironment()) {}, scenario) contextCurrent = Context(TestApplicationEngine(createTestEnvironment()) {}, scenario)
migrations.run()
} }
After { scenario: Scenario -> After { scenario: Scenario ->
migrations.forceAllDown()
contextCurrent.engine.stop(0L, 0L, TimeUnit.MILLISECONDS) contextCurrent.engine.stop(0L, 0L, TimeUnit.MILLISECONDS)
} }
} }

View File

@@ -3,10 +3,14 @@ package feature
import cucumber.api.Scenario import cucumber.api.Scenario
import fr.dcproject.module import fr.dcproject.module
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.TestApplicationCall import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest import io.ktor.server.testing.TestApplicationRequest
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
class Context( class Context(
val engine: TestApplicationEngine, val engine: TestApplicationEngine,
val scenario: Scenario val scenario: Scenario

View File

@@ -1,8 +1,11 @@
package feature package feature
import com.google.gson.Gson import com.google.gson.Gson
import cucumber.api.Scenario
import cucumber.api.java8.En import cucumber.api.java8.En
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations
import io.cucumber.datatable.DataTable import io.cucumber.datatable.DataTable
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
@@ -11,18 +14,46 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.TestApplicationCall import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.setBody import io.ktor.server.testing.setBody
import org.joda.time.DateTime
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.koin.test.KoinTest import org.koin.test.KoinTest
import org.koin.test.inject
import org.opentest4j.AssertionFailedError import org.opentest4j.AssertionFailedError
import java.util.*
import kotlin.test.asserter import kotlin.test.asserter
import feature.Context.Companion.current as currentContext import feature.Context.Companion.current as currentContext
class Request: En, KoinTest { class Request: En, KoinTest {
private val migrations: Migrations by inject()
private val requester: Requester by inject()
init { init {
Before { scenario: Scenario -> // Before { scenario: Scenario ->
// migrations.run()
// }
//
// After { scenario: Scenario ->
// migrations.forceAllDown()
// }
When("I have citizen:") { body: DataTable ->
val user = User(username = "jaque", plainPassword = "azerty")
val data = body.asMap<String, String>(String::class.java, String::class.java)
val citizen = Citizen(
id = UUID.fromString(data["id"]),
name = Citizen.Name(data["firstName"], data["lastName"]),
birthday = DateTime.now(),
user = user
)
val test: TestApplicationEngine.() -> Unit = {
requester
.getFunction("insert_user")
.selectOne(user)
requester
.getFunction("upsert_citizen")
.selectOne(citizen)
} }
After { scenario: Scenario -> currentContext.engine.test()
} }
When("I send a {string} request to {string} with body:") { method: String, uri: String, body: String -> When("I send a {string} request to {string} with body:") { method: String, uri: String, body: String ->
@@ -55,7 +86,7 @@ class Request: En, KoinTest {
Then("the response status code should be {int}") { statusCode: Int -> Then("the response status code should be {int}") { statusCode: Int ->
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call", statusCode, null) val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call", statusCode, null)
with(call) { with(call) {
assertEquals(HttpStatusCode.fromValue(statusCode), response.status()) assertEquals(HttpStatusCode.fromValue(statusCode), response.status(), response.content)
} }
} }

View File

@@ -4,11 +4,15 @@ Feature: articles routes
When I send a "GET" request to "/articles" When I send a "GET" request to "/articles"
Then the response status code should be 200 Then the response status code should be 200
Scenario: The route for get article must response a 200 # Scenario: The route for get article must response a 200
When I send a "GET" request to "/articles/55a24426-139b-4ee7-b1e2-a3d016d66cc2" # When I send a "GET" request to "/articles/55a24426-139b-4ee7-b1e2-a3d016d66cc2"
Then the response status code should be 200 # Then the response status code should be 200
Scenario: The route for get article must response a 200 Scenario: The route for get article must response a 200
Given I have citizen:
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
| firstName | Jaque |
| lastName | Dupuis |
When I send a "POST" request to "/articles" with body: When I send a "POST" request to "/articles" with body:
""" """
{ {
@@ -29,8 +33,3 @@ Feature: articles routes
And the response should contain object: And the response should contain object:
| version_id | 09c418b6-63ba-448b-b38b-502b41cd500e | | version_id | 09c418b6-63ba-448b-b38b-502b41cd500e |
| title | title2 | | title | title2 |
When I send a "GET" request to "/articles/99afd1b1-3555-43c1-80a7-63c56e93d250"
Then the response status code should be 200
And the response should contain object:
| id | 99afd1b1-3555-43c1-80a7-63c56e93d250 |
| title | title2 |