From a4e87304b38ad33932f784f5aef9bb42426a8548 Mon Sep 17 00:00:00 2001 From: Jiaqi Liu Date: Sun, 17 Nov 2024 18:43:31 +0800 Subject: [PATCH] Add tests to Language class (#10) --- README.md | 2 +- .../org/qubitpi/wilhelm/LanguageSpec.groovy | 87 +++++++++ .../filters/LanguageCheckFilterSpec.groovy | 165 ++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 src/test/groovy/org/qubitpi/wilhelm/LanguageSpec.groovy create mode 100644 src/test/groovy/org/qubitpi/wilhelm/web/filters/LanguageCheckFilterSpec.groovy diff --git a/README.md b/README.md index c968bbc..b690ca8 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ dashboard. ### Gateway Registration ```bash -export GATEWAY_PUBLIC_IP=52.53.186.26 +export GATEWAY_PUBLIC_IP= # vocabulary paged & count curl -v -i -s -k -X POST https://api.paion-data.dev:8444/services \ diff --git a/src/test/groovy/org/qubitpi/wilhelm/LanguageSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/LanguageSpec.groovy new file mode 100644 index 0000000..b4f655d --- /dev/null +++ b/src/test/groovy/org/qubitpi/wilhelm/LanguageSpec.groovy @@ -0,0 +1,87 @@ +/* + * Copyright Jiaqi Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.qubitpi.wilhelm + +import spock.lang.Specification + +import java.util.stream.Collectors + +class LanguageSpec extends Specification { + + def 'There are 3 valid languages'() { + expect: + Language.values().size() == 3 + } + + def "Only certain languages are supported"() { + given: "all restricted languages" + List allValidDatabaseNames = ["German", "Ancient Greek", "Latin"] + List allValidPathNames = ["german", "ancientGreek", "latin"] + + expect: "no other database name is allowed" + allValidDatabaseNames.containsAll( + Arrays.stream(Language.values()).map {it -> it.databaseName}.collect(Collectors.toList()) + ) + + and: "no other API path name is allowed" + allValidPathNames.containsAll(Arrays.stream(Language.values()).map {it -> it.pathName}.collect(Collectors.toList())) + } + + def "'#databaseName' can be converted to object"() { + when: "a supported database name is being converted to a Language object" + Language.ofDatabaseName(databaseName) + + then: "no error occurs" + noExceptionThrown() + + where: + _ | databaseName + _ | "German" + _ | "Ancient Greek" + _ | "Latin" + } + + def "'#pathName' can be converted to object"() { + when: "a supported API language name is being converted to a Language object" + Language.ofClientValue(pathName) + + then: "no error occurs" + noExceptionThrown() + + where: + _ | pathName + _ | "german" + _ | "ancientGreek" + _ | "latin" + } + + @SuppressWarnings('GroovyAccessibility') + def "Invalid language cannot construct the object"() { + when: "invalid database name is used to construct the object" + Language.valueOf("foo", (language) -> language.getDatabaseName()) + + then: "error occurs" + Exception exception = thrown(IllegalArgumentException) + exception.message == "'foo' is not a recognized language. Acceptable ones are German, Ancient Greek, Latin" + + when: "invalid API path name is used to construct the object" + Language.valueOf("foo", (language) -> language.getPathName()) + + then: "error occurs" + exception = thrown(IllegalArgumentException) + exception.message == "'foo' is not a recognized language. Acceptable ones are german, ancientGreek, latin" + } +} diff --git a/src/test/groovy/org/qubitpi/wilhelm/web/filters/LanguageCheckFilterSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/web/filters/LanguageCheckFilterSpec.groovy new file mode 100644 index 0000000..65481c2 --- /dev/null +++ b/src/test/groovy/org/qubitpi/wilhelm/web/filters/LanguageCheckFilterSpec.groovy @@ -0,0 +1,165 @@ +/* + * Copyright Jiaqi Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.qubitpi.wilhelm.web.filters + +import jakarta.ws.rs.container.ContainerRequestContext +import jakarta.ws.rs.container.ContainerResponseContext +import jakarta.ws.rs.core.MultivaluedHashMap +import jakarta.ws.rs.core.MultivaluedMap +import jakarta.ws.rs.core.Response +import spock.lang.Specification +import spock.lang.Unroll + +class LanguageCheckFilterSpec extends Specification { + + def "When a request #beingPreflight preflight, request #beingAborted aborted"() { + setup: "a request is being configure for being preflight or not" + ContainerRequestContext request = Mock(ContainerRequestContext) + + if (isPreflight) { + request.getHeaderString("Origin") >> "some origin" + request.getMethod() >> "OPTIONS" + } else { + request.getHeaderString("Origin") >> null + request.getMethod() >> "POST" + } + + when: "the request is pre-matched by CORS filter" + new CorsFilter().filter(request) + + then: "preflight request is bounced back and non-preflight request is accepted" + (isPreflight ? 1 : 0) * request.abortWith(_ as Response) + + where: + _ | isPreflight + _ | true + _ | false + + beingPreflight = isPreflight ? "is" : "is not" + beingAborted = isPreflight ? "is" : "is not" + } + + @Unroll + def "When a request #beingCrossOrigin cross-origin and #beingPreflight preflight, response headers are #expectedResponseHeaders"() { + setup: "a request is being configure for being cross-origin/preflight or not" + ContainerRequestContext request = Mock(ContainerRequestContext) + ContainerResponseContext response = Mock(ContainerResponseContext) + + if (isCrossOrigin) { + request.getHeaderString("Origin") >>> ["some origin"] + } else { + request.getHeaderString("Origin") >>> [null] + } + + if (isPreflight) { + request.getHeaderString("Origin") >>> ["some origin"] + request.getMethod() >> "OPTIONS" + } else { + request.getHeaderString("Origin") >>> [null] + request.getMethod() >> "POST" + } + + MultivaluedMap headers = new MultivaluedHashMap() + response.getHeaders() >>> [headers, headers, headers, headers] + + when: "a response is being processed by CORS filter" + new CorsFilter().filter(request, response) + + then: "the response header gets populated based on cross-origin/preflight nature of the configured request" + response.getHeaders() == expectedResponseHeaders + + where: + isCrossOrigin | isPreflight || expectedResponseHeaders + false | false || [:] + false | true || [:] + true | false || accessControlAllowOrigin() + true | true || accessControlAllowHeaders() << accessControlAllowCredentials() << accessControlAllowMethods() << accessControlAllowOrigin() + + beingCrossOrigin = isCrossOrigin ? "is" : "is not" + beingPreflight = isPreflight ? "is" : "is not" + } + + @SuppressWarnings('GroovyAccessibility') + def "When a request #contains 'Origin' header, it #is a cross origin request"() { + given: + ContainerRequestContext request = Mock(ContainerRequestContext) + + if (isCorssOrigin) { + request.getHeaderString("Origin") >> "some origin" + } else { + request.getHeaderString("Origin") >> null + } + + expect: + CorsFilter.isCrossOriginRequest(request) == isCorssOrigin + + where: + _ || isCorssOrigin + _ || true + _ || false + + contains = isCorssOrigin ? "contains" : "does not contain" + is = isCorssOrigin ? "is" : "is not" + } + + @SuppressWarnings('GroovyAccessibility') + def "When a request #containsOrigin 'Origin' header and method #isOptions 'OPTIONS', it #is a preflight request"() { + given: + ContainerRequestContext request = Mock(ContainerRequestContext) + + if (isCorssOrigin) { + request.getHeaderString("Origin") >> "some origin" + } else { + request.getHeaderString("Origin") >> null + } + + if (methodIsOptions) { + request.getMethod() >> "OPTIONS" + } else { + request.getMethod() >> "POST" + } + + expect: + CorsFilter.isPreflightRequest(request) == isPreflight + + where: + isCorssOrigin | methodIsOptions || isPreflight + false | false || false + false | true || false + true | false || false + true | true || true + + containsOrigin = isCorssOrigin ? "contains" : "does not contain" + isOptions = isCorssOrigin ? "is" : "is not" + is = isPreflight ? "is" : "is not" + } + + static def accessControlAllowHeaders() { + ["Access-Control-Allow-Headers": ["CSRF-Token, X-Requested-By, Authorization, Content-Type"]] + } + + static def accessControlAllowCredentials() { + ["Access-Control-Allow-Credentials": ["true"]] + } + + static def accessControlAllowMethods() { + ["Access-Control-Allow-Methods": ["GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH"]] + } + + static def accessControlAllowOrigin() { + ["Access-Control-Allow-Origin": ["*"]] + } +}