【WIP】JUnit(REST Assured)でQuarkus製アプリをデバッグ実行するとタイムアウトする問題を解決した
検証環境
事象
JUnit(REST Asssured)を使ったテストデバッグでステップ実行を行っていると、 java.net.SocketTimeoutException: Read timed outが発生した。
解決方法
@QuarkusTest public class ApiTest { @Test public void testHelloEndpoint() { given() .config(RestAssured.config() .httpClient(HttpClientConfig.httpClientConfig() .setParam("http.socket.timeout", 1000000))) .when() .log().all() .get("hogehoge-service/hogehoge-service/sampleA/hello") .then() .log().all() .statusCode(200); } }
参考
【Quarkus】OpenAPI Generator(jaxrs-spec)で生成したソースが、エンドポイントがうまく指定できずREST Assuredでテストできない
TL;DR
- 生成対象となるOpenAPIでpathを1種類しか持たないとき、
- クラス単位に設定される@Pathにフルパスが設定される
- メソッド単位にパスは設定されない
- Quarkusが提供するテスト用アノテーション
@TestHTTPResource
は、クラス単位に設定された@Pathまでを注入する - したがって、クラス単位に設定したPathにパスパラメータが含まれている場合、
@TestHTTPResource
を利用したテストができない @TestHTTPResource
を外してフルパスでテストするしかなさそう
環境
動作環境
設定値
library: quarkus additionalProperties: dateLibrary: java8-localdatetime hideGenerationTimestamp: true openApiNullable: false useBeanValidation: true useRuntimeException: true microprofileRestClientVersion: "3.0" serializationLibrary: "jackson" useJakartaEe: true generateBuilders: true interfaceOnly: true useSwaggerAnnotations: false sourceFolder: "src/main/java"
実行コマンドの例
GENERATOR=jaxrs-spec && docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/input/petstore_2path.yaml -g ${GENERATOR} -o /local/out2/${GENERATOR} -c /local/input/config_server.yaml
試したこと(エンドポイントの個数によって生成のされ方が異なる)
エンドポイントが1種類のとき
openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets/{id}: post: summary: "Add a new pet to the store" description: "desc" operationId: "methodpost" parameters: - name: id in: path required: true schema: type: integer description: ユーザID responses: 200: description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/PetsResponse" type: object properties: user: type: object properties: age: type: string sex: type: string put: summary: "Add a new pet to the store" description: "desc" operationId: "methodput" parameters: - name: id in: path required: true schema: type: integer description: ユーザID responses: 200: description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/PetsResponse" type: object components: schemas: Petfoo: required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Petshoge: type: array items: $ref: "#/components/schemas/Petfoo" PetsResponse: properties: id: type: integer format: int64 example: 3 name: type: string example: "wan" tag: type: string example: "inu" Error: required: - code - message properties: code: type: integer format: int32 message: type: string
package org.openapitools.api; import org.openapitools.model.PetsResponse; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; import java.io.InputStream; import java.util.Map; import java.util.List; import jakarta.validation.constraints.*; import jakarta.validation.Valid; @Path("/pets/{id}") @jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", comments = "Generator version: 7.5.0-SNAPSHOT") public interface PetsApi { @POST @Produces({ "application/json" }) PetsResponse methodpost(@PathParam("id") Integer id); @PUT @Produces({ "application/json" }) PetsResponse methodput(@PathParam("id") Integer id); }
エンドポイントが2種類のとき
openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets/{id}: post: summary: "Add a new pet to the store" description: "desc" operationId: "methodpost" parameters: - name: id in: path required: true schema: type: integer description: ユーザID responses: 200: description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/PetsResponse" type: object properties: user: type: object properties: age: type: string sex: type: string put: summary: "Add a new pet to the store" description: "desc" operationId: "methodput" parameters: - name: id in: path required: true schema: type: integer description: ユーザID responses: 200: description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/PetsResponse" type: object /pets/name/{name}: post: summary: "Add a new pet to the store" description: "desc" operationId: "anothermethodpost" parameters: - name: name in: path required: true schema: type: integer description: 顧客名 responses: 200: description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/PetsResponse" type: object properties: user: type: object properties: age: type: string sex: type: string components: schemas: Petfoo: required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Petshoge: type: array items: $ref: "#/components/schemas/Petfoo" PetsResponse: properties: id: type: integer format: int64 example: 3 name: type: string example: "wan" tag: type: string example: "inu" Error: required: - code - message properties: code: type: integer format: int32 message: type: string
package org.openapitools.api; import org.openapitools.model.PetsResponse; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; import java.io.InputStream; import java.util.Map; import java.util.List; import jakarta.validation.constraints.*; import jakarta.validation.Valid; @Path("/pets") @jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", comments = "Generator version: 7.5.0-SNAPSHOT") public interface PetsApi { @POST @Path("/name/{name}") @Produces({ "application/json" }) PetsResponse anothermethodpost(@PathParam("name") Integer name); @POST @Path("/{id}") @Produces({ "application/json" }) PetsResponse methodpost(@PathParam("id") Integer id); @PUT @Path("/{id}") @Produces({ "application/json" }) PetsResponse methodput(@PathParam("id") Integer id); }
参考
Vue.js(Vite)のDockerコンテナを起動したとき、「Error: EACCES: permission denied」エラーが発生したのを解決した
実行環境
- WSL2(Ubuntu20.04)
- Docker
- node.js v21.0.0 (WSL2)
- node.js v16.17.1
事象
docker composeを使ってDockerコンテナでVue.js(Vite)を起動したとき、次のようなエラーが出た。 (WSLから直接viteを起動することはできている)
root@4ce1c6e38951:/src# npm run dev > vue-project@0.0.0 dev > vite error when starting dev server: Error: EACCES: permission denied, rmdir '/src/node_modules/.vite/deps'
解決方法
Dockerコンテナに入ってrm rf node_modules/
npm install
したところ解決した
- 根本的な理由は不明
- WSL側から同コマンドを実行しても解決しなかった
諸々試したこと
- WSL側から
/src/node_modules/.vite/deps
の権限を777に変更してみた(Dockerコンテナからも反映されていることを確認できたが解決には至らず) - WSL側から
node_modules
を削除、npm install
したが解決には至らず
PostgreSQLで存在するはずのテーブル定義の確認を試みると"Did not find any relation named ... "エラーとなる問題を解決した
事象
\dt
コマンドで確認すると確かに存在するUserテーブルが、\d User
コマンドでテーブル定義を確認できず、エラーとなった。
postgres=# \dt List of relations Schema | Name | Type | Owner --------+--------------------+-------+------- public | User | table | user public | _prisma_migrations | table | user public | learning_list | table | user (3 rows) postgres=# \d User Did not find any relation named "User".
環境
PostgreSQL 14(docker image; postgres:14)
原因
PostgreSQLでは、ダブルクォーテーションで囲んで明示的に指示しない限り、大文字は小文字に解釈されるため、
テーブル名を小文字のみ構成にするか、ダブルクォーテーションで\d "Users
のように指定する
引用符が付かない名前は常に小文字に解釈されますが、識別子を引用符で囲むことによって大文字と小文字が区別されるようになります。 例えば、識別子FOO、foo、"foo"はPostgreSQLによれば同じものとして解釈されますが、"Foo"と"FOO"は、これら3つとも、またお互いに違ったものとして解釈されます (PostgreSQLが引用符の付かない名前を小文字として解釈することは標準SQLと互換性がありません。標準SQLでは引用符の付かない名前は大文字に解釈されるべきだとされています。 したがって標準SQLによれば、fooは"FOO"と同じであるべきで、"foo"とは異なるはずなのです。 もし移植可能なアプリケーションを書きたいならば、特定の名前は常に引用符で囲むか、あるいはまったく囲まないかのいずれかに統一することをお勧めします。)
サンプルコードをほぼ転記して動作確認したいだけのフェーズで発生した事象のため、テーブル名を小文字で作り直す修正方針でよさそう。
参考
Mockitoの検証のため環境構築をした
検証環境
次のコマンドでgradleを導入
$ sdk install gradle
$ gradle -v ------------------------------------------------------------ Gradle 8.7 ------------------------------------------------------------ Build time: 2024-03-22 15:52:46 UTC Revision: 650af14d7653aa949fce5e886e685efc9cf97c10 Kotlin: 1.9.22 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 17.0.5 (Eclipse Adoptium 17.0.5+8) OS: Linux 5.10.16.3-microsoft-standard-WSL2 amd64
検証したいディレクトリで$ gradle init
を実行
gradle init Select type of build to generate: 1: Application 2: Library 3: Gradle plugin 4: Basic (build structure only) Enter selection (default: Application) [1..4] 1 Select implementation language: 1: Java 2: Kotlin 3: Groovy 4: Scala 5: C++ 6: Swift Enter selection (default: Java) [1..6] 1 Enter target Java version (min: 7, default: 21): 17 Project name (default: study_mockito2): Select application structure: 1: Single application project 2: Application and library project Enter selection (default: Single application project) [1..2] 1 Select build script DSL: 1: Kotlin 2: Groovy Enter selection (default: Kotlin) [1..2] 2 Select test framework: 1: JUnit 4 2: TestNG 3: Spock 4: JUnit Jupiter Enter selection (default: JUnit Jupiter) [1..4] 4 Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no > Task :init To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.7/samples/sample_building_java_applications.html
build.gradleの設定はこの記事の内容にしたがった
Mockito 3 + JUnit 5 で基本的なモック化とテストをするサンプルコード
plugins { id 'java' } repositories { jcenter() } dependencies { // Junit 5 を導入 // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' // Mockito 3 を導入 // https://mvnrepository.com/artifact/org.mockito/mockito-core testImplementation 'org.mockito:mockito-core:3.6.0' // Mockito による JUnit 5 Extension ライブラリを導入 // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter testImplementation 'org.mockito:mockito-junit-jupiter:3.6.0' } test { // JUnit platform を使う設定 useJUnitPlatform() // テスト時の出力設定 testLogging { // テスト時の標準出力と標準エラー出力を表示する showStandardStreams true // イベントを出力する (TestLogEvent) events 'started', 'skipped', 'passed', 'failed' // 例外発生時の出力設定 (TestExceptionFormat) exceptionFormat 'full' } }
ExpressValidator+Prisma(PostgreSQL)でinput contains invalid characters. Expected ISO-8601 DateTimeエラーを回避するには
ExpressValidator+Prismaを連携させて、PostgreSQLのTIMESTAMP型のカラムへ値を代入しようとした。 また、schema.prisma上はDateTime型である。
RFC3339型(≒ISO8601型)でないと受け付けてくれないため、食べさせる文字列には気を使う必要がある。
ExpressValidatorに適用するカスタムルールはこんな感じでよさそう。
function isRFC3339DateTime(value: string) { if (!value) { return false; } try { const d = new Date(value); d.toISOString(); return true; } catch { return false; } }
router.post("/", [body("used_time").isInt({ min: 3, max: 10 }), body("study_date").custom((value: string) => commonUtil.isRFC3339DateTime(value))], async (req: Request, res: Response) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const result = await memoService.register(req) res.json({ result }) });
あわせて後で読みたい - RFC 3339 vs ISO 8601