diff --git a/build.gradle.kts b/build.gradle.kts index 61aa646..b3796eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,9 +26,12 @@ dependencies { implementation("info.picocli:picocli") implementation("info.picocli:picocli-jansi-graalvm:1.2.0") implementation("org.fusesource.jansi:jansi:2.4.2") + implementation("info.picocli:picocli-shell-jline3:4.7.6") + implementation("org.jline:jline:3.30.5") implementation("io.micronaut:micronaut-http-client") implementation("io.micronaut.picocli:micronaut-picocli") implementation("io.micronaut.serde:micronaut-serde-jackson") + testImplementation("net.datafaker:datafaker:2.5.1") compileOnly("org.projectlombok:lombok") runtimeOnly("ch.qos.logback:logback-classic") runtimeOnly("org.yaml:snakeyaml") @@ -36,7 +39,7 @@ dependencies { application { - mainClass = "org.justserve.BaseCommand" + mainClass = "org.justserve.cli.JustServeCommand" } java { @@ -76,6 +79,7 @@ micronaut { graalvmNative.binaries { named("main") { imageName.set("justserve") + buildArgs.add("--color=always") } } diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..41073ae --- /dev/null +++ b/compose.yml @@ -0,0 +1,26 @@ +services: + linux-shell: + image: ghcr.io/graalvm/native-image-community:21-muslib + container_name: justserve-linux-shell + working_dir: /app + volumes: + - .:/app + env_file: .env + entrypoint: /bin/sh + command: + - -c + - "mkdir -p /temp_app && cp -r . /temp_app/ && cd /temp_app && microdnf install -y findutils && sed -i 's/\r$//' ./gradlew && if [ -t 0 ]; then /bin/bash; else tail -f /dev/null; fi" + stdin_open: true + tty: true + + linux-test: + image: ghcr.io/graalvm/native-image-community:21-muslib + container_name: justserve-linux-test + working_dir: /app + volumes: + - .:/app + env_file: .env + entrypoint: /bin/sh + command: + - -c + - "mkdir -p /temp_app && cp -r . /temp_app/ && cd /temp_app && microdnf install -y findutils && sed -i 's/\r$//' ./gradlew && ./gradlew test" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 50a7dc2..b1b5a5a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ micronautVersion=4.8.3 -justserveCliVersion=0.0.4 \ No newline at end of file +justserveCliVersion=0.0.5-SNAPSHOT +org.gradle.console=rich \ No newline at end of file diff --git a/src/main/java/org/justserve/JustServeClientFilter.java b/src/main/java/org/justserve/auth/JustServeClientFilter.java similarity index 98% rename from src/main/java/org/justserve/JustServeClientFilter.java rename to src/main/java/org/justserve/auth/JustServeClientFilter.java index 8825843..932da88 100644 --- a/src/main/java/org/justserve/JustServeClientFilter.java +++ b/src/main/java/org/justserve/auth/JustServeClientFilter.java @@ -1,4 +1,4 @@ -package org.justserve; +package org.justserve.auth; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; diff --git a/src/main/java/org/justserve/cli/JustServeCommand.java b/src/main/java/org/justserve/cli/JustServeCommand.java new file mode 100644 index 0000000..67a472f --- /dev/null +++ b/src/main/java/org/justserve/cli/JustServeCommand.java @@ -0,0 +1,28 @@ +package org.justserve.cli; + +import io.micronaut.configuration.picocli.PicocliRunner; +import org.justserve.cli.command.BaseCommand; +import org.justserve.cli.command.GetTempPassword; +import org.justserve.cli.util.JustServeVersionProvider; +import picocli.CommandLine.Command; +import picocli.CommandLine.ParameterException; +import picocli.jansi.graalvm.AnsiConsole; + +@Command(subcommands = GetTempPassword.class, + mixinStandardHelpOptions = true, + name = "justserve", versionProvider = JustServeVersionProvider.class, + description = "justserve-cli is a terminal tool to help specialists and admin using JustServe") +public class JustServeCommand extends BaseCommand implements Runnable { + + public static void main(String[] args) { + try (AnsiConsole ignored = AnsiConsole.windowsInstall()) { + PicocliRunner.run(JustServeCommand.class, args); + } + + } + + @Override + public void run() { + throw new ParameterException(spec.commandLine(), "No command specified"); + } +} diff --git a/src/main/java/org/justserve/cli/command/BaseCommand.java b/src/main/java/org/justserve/cli/command/BaseCommand.java new file mode 100644 index 0000000..29da8f6 --- /dev/null +++ b/src/main/java/org/justserve/cli/command/BaseCommand.java @@ -0,0 +1,70 @@ +package org.justserve.cli.command; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.ReflectiveAccess; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +import java.io.PrintWriter; +import java.util.Optional; + +import static picocli.CommandLine.Help.Ansi.AUTO; + +@Command +public class BaseCommand implements ConsoleOutput{ + + @Spec + @ReflectiveAccess + protected CommandSpec spec; + + + @Override + public void out(String message) { + outWriter().ifPresent(writer -> writer.println(AUTO.string(message))); + } + + public void err(String message) { + errWriter().ifPresent(writer -> writer.println(AUTO.string("@|bold,red | Error|@ " + message))); + } + + public void warning(String message) { + outWriter().ifPresent(writer -> writer.println(AUTO.string("@|bold,red | Warning|@ " + message))); + } + + @Override + public void green(String message) { + outWriter().ifPresent(writer -> writer.println(AUTO.string("@|bold,green " + message + "|@"))); + } + + @Override + public void red(String message) { + outWriter().ifPresent(writer -> writer.println(AUTO.string("@|bold,red " + message + "|@"))); + } + + @Override + public boolean showStacktrace() { + return false; + } + + @Override + public boolean verbose() { + return false; + } + + + @NonNull + public Optional outWriter() { + return getSpec().map(spec -> spec.commandLine().getOut()); + } + + @NonNull + public Optional errWriter() { + return getSpec().map(spec -> spec.commandLine().getErr()); + } + + @NonNull + public Optional getSpec() { + return Optional.ofNullable(spec); + } +} diff --git a/src/main/java/org/justserve/cli/command/CommonOptionsMixin.java b/src/main/java/org/justserve/cli/command/CommonOptionsMixin.java new file mode 100644 index 0000000..ce4a634 --- /dev/null +++ b/src/main/java/org/justserve/cli/command/CommonOptionsMixin.java @@ -0,0 +1,34 @@ +package org.justserve.cli.command; + +import io.micronaut.core.annotation.ReflectiveAccess; +import org.justserve.cli.util.JustServeVersionProvider; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +/** + * Mixin that adds help, version and other common options to a command. Example usage: + *
+ * @Command(name = "command")
+ * class App {
+ *     @Mixin
+ *     CommonOptionsMixin commonOptions // adds common options to the command
+ *
+ *     // ...
+ * }
+ * 
+ * + * @author Jonathan Zollinger + * @version 0.0.1 + */ +@Command(mixinStandardHelpOptions = true, versionProvider = JustServeVersionProvider.class) +@SuppressWarnings("checkstyle:VisibilityModifier") +public class CommonOptionsMixin { + + @Option(names = {"-x", "--stacktrace"}, defaultValue = "false", description = "Show full stack trace when exceptions occur.") + @ReflectiveAccess + public boolean showStacktrace; + + @Option(names = {"-v", "--verbose"}, defaultValue = "false", description = "Create verbose output.") + @ReflectiveAccess + public boolean verbose; +} \ No newline at end of file diff --git a/src/main/java/org/justserve/cli/command/ConsoleOutput.java b/src/main/java/org/justserve/cli/command/ConsoleOutput.java new file mode 100644 index 0000000..00c03b0 --- /dev/null +++ b/src/main/java/org/justserve/cli/command/ConsoleOutput.java @@ -0,0 +1,43 @@ +package org.justserve.cli.command; + +public interface ConsoleOutput { + ConsoleOutput NOOP = new ConsoleOutput() { + @Override + public void out(String message) { } + + @Override + public void err(String message) { } + + @Override + public void warning(String message) { } + + @Override + public boolean showStacktrace() { + return false; + } + + @Override + public boolean verbose() { + return false; + } + + }; + + void out(String message); + + void err(String message); + + void warning(String message); + + boolean showStacktrace(); + + boolean verbose(); + + default void green(String message) { + out(message); + } + + default void red(String message) { + out(message); + } +} diff --git a/src/main/java/org/justserve/BaseCommand.java b/src/main/java/org/justserve/cli/command/GetTempPassword.java similarity index 67% rename from src/main/java/org/justserve/BaseCommand.java rename to src/main/java/org/justserve/cli/command/GetTempPassword.java index 76b78ea..399d28a 100644 --- a/src/main/java/org/justserve/BaseCommand.java +++ b/src/main/java/org/justserve/cli/command/GetTempPassword.java @@ -1,7 +1,7 @@ -package org.justserve; +package org.justserve.cli.command; -import io.micronaut.configuration.picocli.PicocliRunner; import io.micronaut.context.annotation.Value; +import io.micronaut.core.annotation.ReflectiveAccess; import io.micronaut.http.HttpResponse; import io.micronaut.http.client.exceptions.HttpClientResponseException; import jakarta.inject.Inject; @@ -10,44 +10,28 @@ import org.justserve.model.UserHashRequestByEmail; import picocli.CommandLine.Command; import picocli.CommandLine.Option; -import picocli.jansi.graalvm.AnsiConsole; -import static org.justserve.JustServePrinter.printError; -import static org.justserve.JustServePrinter.printNormal; +import static org.justserve.cli.util.JustServePrinter.printError; +import static org.justserve.cli.util.JustServePrinter.printNormal; -@Command(name = "justserve", - description = "justserve-cli is a terminal tool to help specialists and admin using JustServe") -public class BaseCommand implements Runnable { +@Command(name = "getTempPassword", description = "get a temporary password for a user") +public class GetTempPassword extends BaseCommand implements Runnable { + @ReflectiveAccess @Option(names = {"-e", "--email"}, description = "email for the user whose temporary password will be generated") String email; - @Option(names = {"version", "--version", "-v"}) - boolean version = false; - - @Value("${micronaut.application.version}") - String justserveCliVersion; - @Inject + @ReflectiveAccess Provider userClientProvider; @Value("${justserve.token}") String token; - public static void main(String[] args) { - try (AnsiConsole ignored = AnsiConsole.windowsInstall()) { - PicocliRunner.run(BaseCommand.class, args); - } - - } - + @Override public void run() { - if (version) { - printNormal(justserveCliVersion); - return; - } HttpResponse response; - if ("i-need-to-be-defined".equals(token) || null == token ) { + if ("i-need-to-be-defined".equals(token) || null == token) { printError(("NO AUTHENTICATION PROVIDED" + System.lineSeparator() + "The Authentication token is not assigned as an environment variable." + System.lineSeparator() + "Please define the environment variable \"JUSTSERVE_TOKEN\" and try again.")); diff --git a/src/main/java/org/justserve/JustServePrinter.java b/src/main/java/org/justserve/cli/util/JustServePrinter.java similarity index 71% rename from src/main/java/org/justserve/JustServePrinter.java rename to src/main/java/org/justserve/cli/util/JustServePrinter.java index 87ea710..643431d 100644 --- a/src/main/java/org/justserve/JustServePrinter.java +++ b/src/main/java/org/justserve/cli/util/JustServePrinter.java @@ -1,4 +1,4 @@ -package org.justserve; +package org.justserve.cli.util; import org.fusesource.jansi.Ansi; @@ -13,13 +13,13 @@ public final class JustServePrinter { private final static Ansi blue = ansi().fgRgb(0, 158, 185); - private final static Ansi orange = ansi().fgRgb(239, 94, 57); + private final static Ansi orange = ansi().fgRgb(255, 140, 0); // OG color is 239, 94, 57 private final static Ansi red = ansi().fgRgb(233, 59, 84); // I definitely eyeballed this one private final static Ansi yellow = ansi().fgRgb(225, 188, 33); private final static Ansi normalStyle = ansi().reset(); - private final static Ansi titleStyle = blue; - private final static Ansi emphasisStyle = orange; + private final static Ansi titleStyle = blue.bold(); + private final static Ansi emphasisStyle = orange.bold(); private final static Ansi warningStyle = yellow; private final static Ansi errorTitleStyle = red.bold(); private final static Ansi errorInfoStyle = ansi().reset(); @@ -32,14 +32,18 @@ private JustServePrinter() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } - private static void jsPrint(String message, Ansi... style) { + private static String applyStyle(String message, Ansi... style) { Ansi styledMessage = stream(style).reduce(ansi(), Ansi::a, Ansi::a); - System.out.println(styledMessage.a(message).reset()); + return styledMessage.a(message).reset().toString(); } + private static void jsPrint(String message, Ansi... style) { + System.out.println(applyStyle(message, style)); + } + + private static void jsPrintErr(String message, Ansi... style) { - Ansi styledMessage = stream(style).reduce(ansi(), Ansi::a, Ansi::a); - System.err.println(styledMessage.a(message).reset()); + System.err.println(applyStyle(message, style)); } /** @@ -51,6 +55,24 @@ public static void printNormal(String message) { jsPrint(message, normalStyle); } + /** + * Returns a String stylized in Orange. + * + * @param message The message to print. + */ + public static String styleTitle(String message) { + return applyStyle(message, titleStyle); + } + + /** + * Returns a String stylized in the designated emphasis style. + * + * @param message The message to print. + */ + public static String styleEmphasis(String message) { + return applyStyle(message, emphasisStyle); + } + /** * Prints an error message to the standard error stream in red. * diff --git a/src/main/java/org/justserve/cli/util/JustServeVersionProvider.java b/src/main/java/org/justserve/cli/util/JustServeVersionProvider.java new file mode 100644 index 0000000..f67d90a --- /dev/null +++ b/src/main/java/org/justserve/cli/util/JustServeVersionProvider.java @@ -0,0 +1,26 @@ +package org.justserve.cli.util; + +import io.micronaut.context.annotation.Value; +import picocli.CommandLine; + +import static org.justserve.cli.util.JustServePrinter.styleEmphasis; +import static org.justserve.cli.util.JustServePrinter.styleTitle; + +public class JustServeVersionProvider implements CommandLine.IVersionProvider { + + @Value("${micronaut.application.version}") + String justserveCliVersion; + + String fancyPrintout = """ + _ _ ____ + | | | | / ___| + | |_ _ ___| |_| (___ ___ _ ____ _____ + | | | | / __| __|\\___ \\ / _ \\ '__\\ \\ / / _ \\ + ___| | |_| \\__ \\ |_ ____) | __/ | \\ V / __/ + |____/ \\__,_|___/\\__|_____/ \\___|_| \\_/ \\___|"""; + + @Override + public String[] getVersion() { + return new String[]{styleEmphasis(fancyPrintout), styleTitle(justserveCliVersion)}; + } +} diff --git a/src/main/resources/schema.yml b/src/main/resources/schema.yml index 94961e2..6beb725 100644 --- a/src/main/resources/schema.yml +++ b/src/main/resources/schema.yml @@ -21,6 +21,26 @@ paths: description: Bad Request '500': description: Internal Server Error + /api/v1/users/securitycontext/bearer: + get: + description: "Retrieves the admin context for a given user (ie org ID's, church unit id's, role levels, etc). if no user is provided, it will return the context for the currently authenticated user" + operationId: getAdminContext + tags: + - User + parameters: + - name: userId + in: query + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: { application/json: { schema: { $ref: '#/components/schemas/SecurityContext' } } } + '401': # No failures for unfound endpoints. querying ID "this is a bad ID" returns the current user's bearer context. + description: Unauthorized + content: { application/json: { schema: { $ref: '#/components/schemas/ProblemDetails' } } } + /api/v1/users/slimSearch: post: operationId: userSearchSlim @@ -36,11 +56,80 @@ paths: schema: { $ref: '#/components/schemas/UserSlimSearchResults' } '400': description: Bad Request + content: { application/json: { schema: { $ref: '#/components/schemas/ProblemDetails' } } } '500': description: Internal Server Error + content: { application/json: { schema: { $ref: '#/components/schemas/ProblemDetails' } } } + + /api/v1/routing/{url}: + get: + operationId: getOrgIdFromSlug + tags: + - DynamicRouting + parameters: [ { name: url, in: path, required: true, schema: { type: string } } ] + responses: + '200': + description: OK + content: { application/json: { schema: { $ref: '#/components/schemas/DynamicRoutingDataResponse' } } } + '404': + description: Not Found + content: { application/json: { schema: { $ref: '#/components/schemas/ProblemDetails' } } } components: schemas: + ChurchCivicGeographyUserRole: + type: object + properties: + churchGeographyId: + type: string + format: uuid + unitId: + type: string + nullable: true + civicGeographyId: + type: string + format: uuid + role: + $ref: '#/components/schemas/Role' + additionalProperties: false + ChurchGeographyUserRole: + type: object + properties: + churchGeographyId: + type: string + format: uuid + unitId: + type: string + nullable: true + role: + $ref: '#/components/schemas/Role' + additionalProperties: false + DynamicRoutingDataResponse: + type: object + properties: + id: + type: string + nullable: true + dynamicRouteType: + description: "The type of entity the route points to." + type: integer + format: int32 + enum: + - 0 + - 1 + x-enum-varnames: + - Organization + - DisasterRelief + additionalProperties: false + OrganizationUserRole: + type: object + properties: + organizationId: + type: string + format: uuid + role: + $ref: '#/components/schemas/Role' + additionalProperties: false UserHashRequest: description: | A request containing either the email or the userid for a user. @@ -325,6 +414,18 @@ components: deletedOn: { type: string, format: date-time, nullable: true } deletedBy: { type: string, format: uuid, nullable: true } additionalProperties: false + OrganizationCivicGeographyUserRole: + type: object + properties: + organizationId: + type: string + format: uuid + role: + $ref: '#/components/schemas/Role' + civicGeographyId: + type: string + format: uuid + additionalProperties: false Location: type: object properties: { latitude: { type: number, format: double }, @@ -363,4 +464,52 @@ components: "globalAdmin", "globalLeadAdmin", "developer" - ] \ No newline at end of file + ] + ProblemDetails: + type: object + properties: + type: + type: string + format: uri + nullable: true + title: + type: string + nullable: true + status: + type: integer + format: int32 + nullable: true + traceId: { type: string, nullable: true } + SecurityContext: + type: object + properties: + userId: + type: string + format: uuid + userRepresentativeForOrganizations: + type: array + items: + type: string + format: uuid + nullable: true + churchGeographies: + type: object + additionalProperties: + $ref: '#/components/schemas/ChurchGeographyUserRole' + nullable: true + civicGeographies: + type: object + additionalProperties: + $ref: '#/components/schemas/ChurchCivicGeographyUserRole' + nullable: true + organizationRoles: + type: object + additionalProperties: + $ref: '#/components/schemas/OrganizationUserRole' + nullable: true + organizationCivicGeographyUserRoles: + type: array + items: + $ref: '#/components/schemas/OrganizationCivicGeographyUserRole' + nullable: true + additionalProperties: false \ No newline at end of file diff --git a/src/test/groovy/org/justserve/JustServeSpec.groovy b/src/test/groovy/org/justserve/JustServeSpec.groovy index 75fbe0d..78073b1 100644 --- a/src/test/groovy/org/justserve/JustServeSpec.groovy +++ b/src/test/groovy/org/justserve/JustServeSpec.groovy @@ -3,12 +3,16 @@ package org.justserve import io.micronaut.context.ApplicationContext import io.micronaut.context.env.Environment import io.micronaut.test.extensions.spock.annotation.MicronautTest +import net.datafaker.Faker import spock.lang.Shared import spock.lang.Specification @MicronautTest() class JustServeSpec extends Specification { + @Shared + Faker faker + @Shared ApplicationContext ctx, noAuthCtx @@ -16,6 +20,7 @@ class JustServeSpec extends Specification { String userEmail def setupSpec() { + faker = new Faker() userEmail = "jimmy@justserve.org" if (null != System.getenv("JUSTSERVE_TOKEN")) { diff --git a/src/test/groovy/org/justserve/client/DynamicRoutingClientSpec.groovy b/src/test/groovy/org/justserve/client/DynamicRoutingClientSpec.groovy new file mode 100644 index 0000000..369ba4b --- /dev/null +++ b/src/test/groovy/org/justserve/client/DynamicRoutingClientSpec.groovy @@ -0,0 +1,37 @@ +package org.justserve.client + +import io.micronaut.http.HttpResponse +import io.micronaut.http.HttpStatus +import org.justserve.JustServeSpec +import org.justserve.model.DynamicRoutingDataResponse +import spock.lang.Shared + +class DynamicRoutingClientSpec extends JustServeSpec { + + @Shared + DynamicRoutingClient noAuthClient, authClient + + + def setupSpec() { + noAuthClient = noAuthCtx.getBean(DynamicRoutingClient) + authClient = ctx.getBean(DynamicRoutingClient) + } + + def "get orgId for #url"() { + when: + HttpResponse response = client.getOrgIdFromSlug(url) + + then: + response.status() == expectedStatus + if (expectedStatus == HttpStatus.OK) { + response.body().id != null + } + + where: + url | expectedStatus | client + "blankparkzoo" | HttpStatus.OK | authClient + "blankparkzoo" | HttpStatus.OK | noAuthClient + "1234" | HttpStatus.NOT_FOUND | authClient + "1234" | HttpStatus.NOT_FOUND | noAuthClient + } +} diff --git a/src/test/groovy/org/justserve/client/UserClientSpec.groovy b/src/test/groovy/org/justserve/client/UserClientSpec.groovy index f868993..166205f 100644 --- a/src/test/groovy/org/justserve/client/UserClientSpec.groovy +++ b/src/test/groovy/org/justserve/client/UserClientSpec.groovy @@ -1,13 +1,13 @@ package org.justserve.client - import io.micronaut.http.HttpResponse -import io.micronaut.http.HttpStatus import io.micronaut.http.client.exceptions.HttpClientResponseException import org.justserve.JustServeSpec import org.justserve.model.UserHashRequestByEmail import spock.lang.Shared +import static io.micronaut.http.HttpStatus.* + class UserClientSpec extends JustServeSpec { @Shared @@ -19,7 +19,35 @@ class UserClientSpec extends JustServeSpec { userClient = ctx.getBean(UserClient) } - def "get tempPassword for #email and "() { + def "get admin context for #description"() { + when: + def response = null + def thrownException = null + try { + response = client.getAdminContext(UUID.fromString(uid)) + } catch (HttpClientResponseException e) { + thrownException = e + } + + then: + if (thrownException) { + thrownException.status == expectedStatus + } else { + response.status == expectedStatus + response.body() != null + if (expectedStatus == OK) { + response.body().userId == UUID.fromString(uid) + } + } + + where: + description | uid | client | expectedStatus + "a valid user" | "e23d029c-25f6-4c93-aada-dcbdc6d50c2c" | userClient | OK + "an invalid user" | faker.internet().uuid() | userClient | NOT_FOUND + "a valid user no-auth" | "e23d029c-25f6-4c93-aada-dcbdc6d50c2c" | noAuthUserClient | UNAUTHORIZED + } + + def "get tempPassword for #email"() { when: HttpResponse response = null def caughtException = null @@ -46,7 +74,7 @@ class UserClientSpec extends JustServeSpec { where: expectedResponse | email | expectedException | expectedMessage | client | _ - HttpStatus.OK | userEmail | null | null | userClient | _ + OK | userEmail | null | null | userClient | _ null | "notanemail@mail.moc" | HttpClientResponseException | "\"status\":500" | userClient | _ null | userEmail | HttpClientResponseException | "\"status\":401" | noAuthUserClient | _ null | "notanemail@mail.moc" | HttpClientResponseException | "\"status\":401" | noAuthUserClient | _ diff --git a/src/test/groovy/org/justserve/BaseCommandSpec.groovy b/src/test/groovy/org/justserve/command/BaseCommandSpec.groovy similarity index 59% rename from src/test/groovy/org/justserve/BaseCommandSpec.groovy rename to src/test/groovy/org/justserve/command/BaseCommandSpec.groovy index 0786f85..200a627 100644 --- a/src/test/groovy/org/justserve/BaseCommandSpec.groovy +++ b/src/test/groovy/org/justserve/command/BaseCommandSpec.groovy @@ -1,7 +1,9 @@ -package org.justserve +package org.justserve.command import io.micronaut.configuration.picocli.PicocliRunner import io.micronaut.context.ApplicationContext +import org.justserve.JustServeSpec +import org.justserve.cli.JustServeCommand import spock.lang.Shared import spock.lang.Unroll @@ -10,7 +12,7 @@ import java.util.regex.Pattern class BaseCommandSpec extends JustServeSpec { @Shared - Pattern cliVersion, ansiRegex, blankRegex, successRegex, errorRegex, tokenNotSetRegex + Pattern cliVersion, blankRegex, successRegex, errorRegex, tokenNotSetRegex def setupSpec() { def props = new Properties() @@ -18,7 +20,14 @@ class BaseCommandSpec extends JustServeSpec { props.load(stream) } def ansi = "\\u001B\\[[;\\d]*m" - cliVersion = Pattern.compile("^${ansi}" + props.getProperty('justserveCliVersion') + "${ansi}\\s*\$") + def staticText = """ _ _ ____ + | | | | / ___| + | |_ _ ___| |_| (___ ___ _ ____ _____ + | | | | / __| __|\\___ \\ / _ \\ '__\\ \\ / / _ \\ + ___| | |_| \\__ \\ |_ ____) | __/ | \\ V / __/ +|____/ \\__,_|___/\\__|_____/ \\___|_| \\_/ \\___|""" + cliVersion = Pattern.compile("^${ansi}${Pattern.quote(staticText)}${ansi}\\s*${ansi}" + + Pattern.quote(props.getProperty('justserveCliVersion')) + "${ansi}\\s*\$") blankRegex = Pattern.compile "^\\s*\$" successRegex = Pattern.compile("^${ansi}\\w+${ansi}\\s*\$") errorRegex = Pattern.compile("(?is)^${ansi}received an unexpected response from JustServe:${ansi}.*\\d+ \\(.*?\\)${ansi}\\s*\$") @@ -27,31 +36,6 @@ class BaseCommandSpec extends JustServeSpec { "${ansi}Please define the environment variable \"JUSTSERVE_TOKEN\" and try again\\.${ansi}\\s*\$") } - @Unroll("command with args: calling 'justserve #flag #email' works as expected") - def "commands to query temporary password should behave as expected with or without authentication"() { - when: - def (outputStream, errorStream) = executeCommand(context as ApplicationContext, [flag, email] as String[]) - - then: - if (context == noAuthCtx) { - verifyAll { - outputStream.matches(blankRegex) - errorStream.matches(tokenNotSetRegex) - } - } else if (userEmail.equalsIgnoreCase(email as String)) { - verifyAll { - outputStream.matches(successRegex) - errorStream.matches(blankRegex) - } - } else { - verifyAll { - outputStream.matches(blankRegex) - errorStream.matches(errorRegex) - } - } - where: - [flag, email, context] << [['-e', '--email'], [userEmail, "notanemail@mail.moc"], [noAuthCtx, ctx]].combinations() - } @Unroll def "querying version returns current version, with or without authentication"() { @@ -65,7 +49,7 @@ class BaseCommandSpec extends JustServeSpec { } where: - [args, context] << [[['-v'], ['--version'], ['version']], [noAuthCtx, ctx]].combinations() + [args, context] << [[['-V'], ['--version']], [noAuthCtx, ctx]].combinations() } /** @@ -82,12 +66,8 @@ class BaseCommandSpec extends JustServeSpec { OutputStream err = new ByteArrayOutputStream() System.setOut(new PrintStream(out)) System.setErr(new PrintStream(err)) - PicocliRunner.run(BaseCommand.class, ctx, args) + PicocliRunner.run(JustServeCommand.class, ctx, args) return new String[]{out.toString(), err.toString()} } - String stripColor(String string) { - return ansiRegex.matcher(string).replaceAll("") - } - } diff --git a/src/test/groovy/org/justserve/command/GetTempPasswordSpec.groovy b/src/test/groovy/org/justserve/command/GetTempPasswordSpec.groovy new file mode 100644 index 0000000..305d8da --- /dev/null +++ b/src/test/groovy/org/justserve/command/GetTempPasswordSpec.groovy @@ -0,0 +1,33 @@ +package org.justserve.command + +import io.micronaut.context.ApplicationContext +import spock.lang.Unroll + +class GetTempPasswordSpec extends BaseCommandSpec { + + @Unroll("command with args: calling 'justserve #flag #email' works as expected") + def "commands to query temporary password should behave as expected with or without authentication"() { + when: + def (outputStream, errorStream) = executeCommand(context as ApplicationContext, ["getTempPassword", flag, email] as String[]) + + then: + if (context == noAuthCtx) { + verifyAll { + outputStream.matches(blankRegex) + errorStream.matches(tokenNotSetRegex) + } + } else if (userEmail.equalsIgnoreCase(email as String)) { + verifyAll { + outputStream.matches(successRegex) + errorStream.matches(blankRegex) + } + } else { + verifyAll { + outputStream.matches(blankRegex) + errorStream.matches(errorRegex) + } + } + where: + [flag, email, context] << [['-e', '--email'], [userEmail, "notanemail@mail.moc"], [noAuthCtx, ctx]].combinations() + } +}