I am trying to put in place a unit test for websocket. From the doc, I should be able to use WS
See below a sscce
package com.streamingout
import akka.http.scaladsl.model.ws.TextMessage
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.PathMatchers.Rest
import akka.http.scaladsl.testkit.{ScalatestRouteTest, WSProbe}
import akka.stream.scaladsl.{Flow, Sink, Source}
import org.scalatest.{FlatSpec, Matchers}
class Test extends FlatSpec with Matchers with ScalatestRouteTest{
//--------------- Flow ---------------
def flow = {
import scala.concurrent.duration._
val source = Source.tick(initialDelay = 0 second, interval = 1 second, tick = TextMessage("tick"))
Flow.fromSinkAndSource(Sink.ignore, source)
}
//-------------- Routing ------------
def route = {
path("/wskt") {
println("websocket ws")
handleWebSocketMessages(flow)
} ~
path(Rest) { pathRest =>
println("path Rest")
getFromFile(s"webapp/$pathRest")
}
}
// create a testing probe representing the client-side
val wsClient = WSProbe()
// WS creates a WebSocket request for testing
WS("/wskt", wsClient.flow) ~> route ~> check {
// check response for WS Upgrade headers
isWebSocketUpgrade shouldEqual true
}
}
When I run the test, I can see in my console the path Rest message, meaning that WS doesnt upgrade to Websocket.
Anyone knows what is wrong with my code?
I am using akka 2.4.7
Thank you
To make the above code work, in the route, the path /wkst should be without any leading slash
def route = {
path("wskt") {
println("websocket ws")
handleWebSocketMessages(flow)
} ~
path(Rest) { pathRest =>
println("path Rest")
getFromFile(s"webapp/$pathRest")
}
}
Related
I am trying add a qr code scanning functionality to my react app. I am using #zxing(https://www.npmjs.com/package/#zxing/browser & https://www.npmjs.com/package/#zxing/library) packages.
Following the readme, here's my js code. I have hosted the application on aws so its SSL covered. But I can't seem to figure out the issue. I have read the git repo of both and the functions do exists(https://github.com/zxing-js/browser/tree/master/src/readers)
import React, { useState, useEffect } from "react";
import {
NotFoundException,
ChecksumException,
FormatException
} from "#zxing/library";
import { BrowserQRCodeReader, BrowserCodeReader } from '#zxing/browser';
export default function() {
var qrCodeReader = null;
var codeReader = null;
var sourceSelect = null;
console.log("ZXing code reader initialized");
useEffect(() => {
codeReader = new BrowserCodeReader();
qrCodeReader = new BrowserQRCodeReader();
console.log(codeReader.listVideoInputDevices()); // ISSUE: RETURNS -> listVideoInputDevices() is not a fuction
console.log(qrCodeReader.listVideoInputDevices()); // ISSUE: RETURNS -> listVideoInputDevices() is not a fuction
console.log("Code Reader", codeReader); // ISSUE: SEE IMAGE BELOW
console.log("QR Code Reader", qrCodeReader); // ISSUE: SEE IMAGE BELOW
}, []);
Instead of using the import try using:
`
const zxing = require('zxing-js/library');
`
Update: Link to repo is moved to answer because repo is now updated with code from answer below.
Problem description
Current code is working, but it is using gcloud beta emulators pubsub from google/cloud-sdk for integration tests.
Integration tests are slow due to the size of the google/cloud-sdk image
pubsub emulator has to run on a fixed port, there seems to be no way to tell Micronaut which port the emulator is running on
I'll need to set the following environment variable in maven-surefire-plugin.
<environmentVariables>
<PUBSUB_EMULATOR_HOST>localhost:8085</PUBSUB_EMULATOR_HOST>
</environmentVariables>
How this can be done in Spring Boot
According to Test Containers | Gcloud Module, the correct way of implementing integration tests with PubSubEmulatorContainer in Spring Boot is like this:
https://github.com/saturnism/testcontainers-gcloud-examples/blob/main/springboot/pubsub-example/src/test/java/com/example/springboot/pubsub/PubSubIntegrationTests.java
This will bring up the container on a random port, and that is possible because of DynamicPropertyRegistry in Spring. It seems that Micronaut is missing this possibility.
Doc: https://www.testcontainers.org/modules/gcloud/
I'm looking for a working example of a JUnit5 or Spock integration test implemented in Micronaut 3.x that is using PubSubEmulatorContainer like described in the above doc.
Related doc: https://micronaut-projects.github.io/micronaut-gcp/latest/guide/#emulator
There are some comments on GitHub around configuring TransportChannelProvider. I'm able to inject an instance and inspect it, but I still haven't found out exactly what to do.
These are the closest leads so far:
https://github.com/micronaut-projects/micronaut-gcp/issues/257
https://github.com/micronaut-projects/micronaut-gcp/pull/259
TL;DR
We'll need to start the testcontainer first, get emulator host address and then call ApplicationContext.run like this:
applicationContext = ApplicationContext.run(
["pubsub.emulator.host": emulatorHost])
Small Github repo with example code: https://github.com/roar-skinderviken/pubsub-emulator-demo
Long answer with code
I finally managed to make a working solution using Micronaut 3.0.2 and Spock. A related Micronaut PR got me on track, together with this article: Micronaut Testing Best Practices https://objectcomputing.com/files/9815/9259/7089/slide_deck_Micronaut_Testing_Best_Practices_webinar.pdf
First a PubSubEmulator class (Groovy)
package no.myproject.testframework.testcontainers
import org.testcontainers.containers.PubSubEmulatorContainer
import org.testcontainers.utility.DockerImageName
class PubSubEmulator {
static PubSubEmulatorContainer pubSubEmulatorContainer
static init() {
if (pubSubEmulatorContainer == null) {
pubSubEmulatorContainer = new PubSubEmulatorContainer(
DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:emulators"))
pubSubEmulatorContainer.start()
}
}
}
Then a fixture for PubSubEmulator (Groovy)
package no.myproject.testframework.testcontainers
trait PubSubEmulatorFixture {
Map<String, Object> getPubSubConfiguration() {
if (PubSubEmulator.pubSubEmulatorContainer == null || !PubSubEmulator.pubSubEmulatorContainer.isRunning()) {
PubSubEmulator.init()
}
[
"pubsub.emulator-host": PubSubEmulator.pubSubEmulatorContainer.getEmulatorEndpoint()
]
}
}
Then a specification class (Groovy) that starts the container, creates a topic and a subscription.
The clue here is to pass in pubsub.emulator.host as part of the configuration when calling ApplicationContext.run.
Remaining part of the code is very similar to the Spring Boot example I linked to in my question.
package no.myproject.testframework
import com.google.api.gax.core.NoCredentialsProvider
import com.google.api.gax.grpc.GrpcTransportChannel
import com.google.api.gax.rpc.FixedTransportChannelProvider
import com.google.cloud.pubsub.v1.SubscriptionAdminClient
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings
import com.google.cloud.pubsub.v1.TopicAdminClient
import com.google.cloud.pubsub.v1.TopicAdminSettings
import com.google.pubsub.v1.ProjectSubscriptionName
import com.google.pubsub.v1.PushConfig
import com.google.pubsub.v1.TopicName
import io.grpc.ManagedChannelBuilder
import io.micronaut.context.ApplicationContext
import no.myproject.configuration.GcpConfigProperties
import no.myproject.configuration.PubSubConfigProperties
import no.myproject.testframework.testcontainers.PubSubEmulatorFixture
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
abstract class PubSubSpecification extends Specification
implements PubSubEmulatorFixture, EnvironmentFixture {
#AutoCleanup
#Shared
EmbeddedServer embeddedServer
#AutoCleanup
#Shared
ApplicationContext applicationContext
def setupSpec() {
// start the pubsub emulator
def emulatorHost = getPubSubConfiguration().get("pubsub.emulator-host")
// start a temporary applicationContext in order to read config
// keep any pubsub subscriptions out of context at this stage
applicationContext = ApplicationContext.run()
def gcpConfigProperties = applicationContext.getBean(GcpConfigProperties)
def pubSubConfigProperties = applicationContext.getBean(PubSubConfigProperties)
def channel = ManagedChannelBuilder.forTarget("dns:///" + emulatorHost)
.usePlaintext()
.build()
def channelProvider =
FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel))
// START creating topic
def topicAdminClient =
TopicAdminClient.create(
TopicAdminSettings.newBuilder()
.setCredentialsProvider(NoCredentialsProvider.create())
.setTransportChannelProvider(channelProvider)
.build())
def topic = TopicName.of(
gcpConfigProperties.getProjectId(),
pubSubConfigProperties.getTopicName())
try {
topicAdminClient.createTopic(topic)
} catch (AlreadyExistsException) {
// this is fine, already created
topicAdminClient.getTopic(topic)
}
// START creating subscription
pubSubConfigProperties.getSubscriptionNames().forEach(it -> {
def subscription =
ProjectSubscriptionName.of(gcpConfigProperties.getProjectId(), it)
def subscriptionAdminClient =
SubscriptionAdminClient.create(
SubscriptionAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(NoCredentialsProvider.create())
.build())
try {
subscriptionAdminClient
.createSubscription(
subscription,
topic,
PushConfig.getDefaultInstance(),
100)
System.out.println("Subscription created " + subscriptionAdminClient.getSubscription(subscription))
} catch (AlreadyExistsException) {
// this is fine, already created
subscriptionAdminClient.getSubscription(subscription)
}
})
channel.shutdown()
// stop the temporary applicationContext
applicationContext.stop()
// start the actual applicationContext
embeddedServer = ApplicationContext.run(
EmbeddedServer,
[
'spec.name' : "PubSubEmulatorSpec",
"pubsub.emulator.host": emulatorHost
],
environments)
applicationContext = embeddedServer.applicationContext
}
}
Then a factory class (Groovy) for mocking credentials
package no.myproject.pubsub
import com.google.auth.oauth2.AccessToken
import com.google.auth.oauth2.GoogleCredentials
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.annotation.Requires
import javax.inject.Singleton
#Factory
#Requires(property = 'spec.name', value = 'PubSubEmulatorSpec')
class EmptyCredentialsFactory {
#Singleton
#Replaces(GoogleCredentials)
GoogleCredentials mockCredentials() {
return GoogleCredentials.create(new AccessToken("", new Date()))
}
}
And finally, a Spock test spec.
package no.myproject.pubsub
import no.myproject.testframework.PubSubSpecification
import java.util.stream.IntStream
class PubSubIntegrationSpec extends PubSubSpecification {
def NUMBER_OF_MESSAGES_IN_TEST = 5
def DELAY_IN_MILLISECONDS_PER_MSG = 100
def "when a number of messages is sent, same amount of messages is received"() {
given:
def documentPublisher = applicationContext.getBean(DocumentPublisher)
def listener = applicationContext.getBean(IncomingDocListenerWithAck)
def initialReceiveCount = listener.getReceiveCount()
when:
IntStream.rangeClosed(1, NUMBER_OF_MESSAGES_IN_TEST)
.forEach(it -> documentPublisher.send("Hello World!"))
// wait a bit in order to let all messages propagate through the queue
Thread.sleep(NUMBER_OF_MESSAGES_IN_TEST * DELAY_IN_MILLISECONDS_PER_MSG)
then:
NUMBER_OF_MESSAGES_IN_TEST == listener.getReceiveCount() - initialReceiveCount
}
}
The chosen answer is a good deal more complicated than necessary, and it also contains numerous typos. A better answer can be found via the Micronaut GCP codebase itself, with the key bit being:
class IntegrationTestSpec extends Specification {
static CONTAINER_PORT = -1
static CredentialsProvider CREDENTIALS_PROVIDER
static TransportChannelProvider TRANSPORT_CHANNEL_PROVIDER
static PubSubResourceAdmin pubSubResourceAdmin
static GenericContainer pubSubContainer = new GenericContainer("google/cloud-sdk:292.0.0")
.withCommand("gcloud", "beta", "emulators", "pubsub", "start", "--project=test-project",
"--host-port=0.0.0.0:8085")
.withExposedPorts(8085)
.waitingFor(new LogMessageWaitStrategy().withRegEx("(?s).*Server started, listening on.*"))
static {
pubSubContainer.start()
CONTAINER_PORT = pubSubContainer.getMappedPort(8085)
CREDENTIALS_PROVIDER = NoCredentialsProvider.create()
def host = "localhost:" + IntegrationTest.CONTAINER_PORT
ManagedChannel channel = ManagedChannelBuilder.forTarget(host).usePlaintext().build()
TRANSPORT_CHANNEL_PROVIDER =
FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel))
pubSubResourceAdmin = new PubSubResourceAdmin(TRANSPORT_CHANNEL_PROVIDER, CREDENTIALS_PROVIDER)
}
}
You'd then just extend that class anywhere you wanted to make use of PubSub. The following is slightly cleaner example that I came up with which manages creating a topic as well during test startup:
#Slf4j
abstract class PubSubSpec extends Specification implements TestPropertyProvider {
static final String cloudSdkName = System.getenv('CLOUD_SDK_IMAGE') ?: "gcr.io/google.com/cloudsdktool/cloud-sdk:emulators"
static final DockerImageName cloudSdkImage = DockerImageName.parse(cloudSdkName)
static final PubSubEmulatorContainer pubsubEmulator = new PubSubEmulatorContainer(cloudSdkImage)
static {
pubsubEmulator.start()
ManagedChannel channel = ManagedChannelBuilder.forTarget(pubsubEmulator.getEmulatorEndpoint()).usePlaintext().build()
try {
TransportChannelProvider channelProvider = FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel))
CredentialsProvider credentialsProvider = NoCredentialsProvider.create()
TopicAdminClient topicClient = TopicAdminClient.create(
TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
)
TopicName topicName = TopicName.of("project-id", "project-topic")
topicClient.createTopic(topicName)
} finally {
channel.shutdown()
}
}
#Override
Map<String, String> getProperties() {
[
"pubsub.emulator.host": pubsubEmulator.getEmulatorEndpoint()
]
}
}
I'm working on a custom SPFX commandset. It opens a dialog with an iframe to an 3rth party platform. I'm able to receive a json through a postmessage. From this json, I convert it's data to a file, with it's proper metadata. All of this works like a charm... Except...
Now I want to upload this file to a document library, and it drives me crazy.
I'm referencing:
import "#pnp/polyfill-ie11";
import { ConsoleListener, Logger, LogLevel } from "#pnp/logging";
import { sp } from "#pnp/sp";
import { Web } from "#pnp/sp/webs";
import "#pnp/sp/webs";
import "#pnp/sp/files";
import "#pnp/sp/folders";
import { Base64 } from 'js-base64';
In my dialog component, I try to upload the file with web.getFolderByServerRelativeUrl. But this method is failing, and I really don't understand why.... Looking at the pnp reference (https://pnp.github.io/pnpjs/sp/files/), It seems like the right way.
var file = Base64.atob(response.Data);
console.log("File length : " + file.length);
let web = Web("https://MyTenant.sharepoint.com/sites/Customer"); // this is successful
await web.getFolderByServerRelativeUrl("/sites/Customer/Shared%20Documents/")
.files.add(response.fileName, file, true); // this fails
The context is set on the CommandSet onInit()
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized myCommandSet');
pnpSetup({
spfxContext: this.context
});
return Promise.resolve();
}
Hope you guys and girls can point me in the right direction...
EDIT:
Error:
HTTP400: INVALID REQUEST - The request could not be processed by the server
due to an invalid syntax
POST - https://MyDevTenant.sharepoint.com/sites/customer/
_api/web/getFolderByServerRelativeUrl
('%2Fsites%2Customer%2FShared%2520Documents%2F')
/files/add(overwrite=true,url='')
Is it the url from the documentlibrary that messes things up?
Thanks to Willman for giving me a right direction.
This did the trick:
import { sp, Web, IWeb } from "#pnp/sp/presets/all";
import "#pnp/sp/webs";
import "#pnp/sp/lists";
import "#pnp/sp/files";
import "#pnp/sp/folders";
const web = await sp.web();
const list = sp.web.getList("Documents");
const listId = await list.select("Id")();
await sp.web.lists.getById(listId.Id).rootFolder.files.add(docname, file, true);
Thankfully, Google announced the export logic from cloud Datastore. I would like to set up schedule-export in my platform. However, it's not Python, but Java. So I need to use cron.xml and Java logic to design this logic.
Is there any reference to design Datastore export logic (cloud_datastore_admin.py) in Java? Especially, I need to transform this part in Java
app = webapp2.WSGIApplication(
[
('/cloud-datastore-export', Export),
], debug=True)
https://cloud.google.com/datastore/docs/schedule-export
You can create the skeleton for App Egnine by following these instructions.
Once you have the skeleton, add something like this to handle export requests:
CloudDatastoreExport.java
package com.google.example.datastore;
import com.google.appengine.api.appidentity.AppIdentityService;
import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
import com.google.apphosting.api.ApiProxy;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Logger;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
#WebServlet(name = "CloudDatastoreExport", value = "/cloud-datastore-export")
public class CloudDatastoreExport extends HttpServlet {
private static final Logger log = Logger.getLogger(CloudDatastoreExport.class.getName());
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Verify outputURL parameter
String outputUrlPrefix = request.getParameter("output_url_prefix");
if (outputUrlPrefix == null || !outputUrlPrefix.matches("^gs://.*")) {
response.setStatus(HttpServletResponse.SC_CONFLICT);
response.setContentType("text/plain");
response.getWriter().println("Error: Must provide a valid output_url_prefix.");
} else {
// Get project ID
String projectId = ApiProxy.getCurrentEnvironment().getAppId();
// Remove partition information to get plain app ID
String appId = projectId.replaceFirst("(.*~)", "");
// Get access token
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/datastore");
final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
final AppIdentityService.GetAccessTokenResult accessToken =
appIdentity.getAccessToken(scopes);
// Read export parameters
// If output prefix does not end with slash, add a timestamp
if (!outputUrlPrefix.substring(outputUrlPrefix.length() - 1).contentEquals("/")) {
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
outputUrlPrefix = outputUrlPrefix + "/" + timeStamp + "/";
}
String[] namespaces = request.getParameterValues("namespace_id");
String[] kinds = request.getParameterValues("kind");
// Build export request
JSONObject exportRequest = new JSONObject();
exportRequest.put("output_url_prefix", outputUrlPrefix);
JSONObject entityFilter = new JSONObject();
if (kinds != null) {
JSONArray kindsJSON = new JSONArray(kinds);
entityFilter.put("kinds", kinds);
}
if (namespaces != null) {
JSONArray namespacesJSON = new JSONArray(namespaces);
entityFilter.put("namespaceIds", namespacesJSON);
}
exportRequest.put("entityFilter", entityFilter);
URL url = new URL("https://datastore.googleapis.com/v1/projects/" + appId + ":export");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Authorization", "Bearer " + accessToken.getAccessToken());
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
exportRequest.write(writer);
writer.close();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONTokener exportResponseTokens = new JSONTokener(connection.getInputStream());
JSONObject exportResponse = new JSONObject(exportResponseTokens);
response.setContentType("text/plain");
response.getWriter().println("Export started:\n" + exportResponse.toString(4));
} else {
InputStream s = connection.getErrorStream();
InputStreamReader r = new InputStreamReader(s, StandardCharsets.UTF_8);
String errorMessage =
String.format(
"got error (%d) response %s from %s",
connection.getResponseCode(), CharStreams.toString(r), connection.toString());
log.warning(errorMessage);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("text/plain");
response.getWriter().println("Failed to initiate export.");
}
}
}
}
You can use the same cron.yaml from the docs:
cron:
- description: "Daily Cloud Datastore Export"
url: /cloud-datastore-export?namespace_id=&output_url_prefix=gs://BUCKET_NAME[/NAMESPACE_PATH]
target: cloud-datastore-admin
schedule: every 24 hours
Use gcloud to deploy the cron job:
gcloud app deploy cron.yaml
Make sure you complete this part to give GAE export and bucket permissions or else
you'll get permission denied errors:
https://cloud.google.com/datastore/docs/schedule-export#before_you_begin
The code snippet you showed is just a part of the typical GAE app skeleton specific for 1st generation standard environment python apps. You can easily recognize it in the main.py section of the python quickstart Hello World code review.
The code initializes the app variable (from the main python module, i.e. the main.py file) which is referenced in the app.yaml handler config as script: main.app.
The corresponding java app skeleton is significantly different, see the java quickstart Hello World code review. But no worries, you shouldn't need to specifically transform that code snippet, you just need to build your java app skeleton and focus on what the app handler actually does - making those POST requests to the datastore. Sorry I can't help more, but I'm not a java user.
What I really realized is that app.yaml is like Web.xml in java
and cloud-datastore-export is a servlet that communicates with gae to export data but I can't do more
While refactoring from ReactiveMongo.0.11.1 to ReactiveMongo 0.12.0 in reference of https://github.com/AhmadMelegy/play-silhouette-reactivemongo-angular-seed compilation failed:
def configure() {
bind[DB].toInstance
{
import com.typesafe.config.ConfigFactory
import reactivemongo.api.DefaultDB
import scala.concurrent.ExecutionContext.Implicits.global
import scala.collection.JavaConversions._
import scala.concurrent.Future
val config = ConfigFactory.load
val driver = new MongoDriver
val connection = driver.connection(
config.getStringList("mongodb.servers"),
MongoConnectionOptions(),
Seq()
)
connection.database(config.getString("mongodb.db"))
}
How to resolve this issue?
required: reactivemongo.api.DB
found : scala.concurrent.Future[reactivemongo.api.DefaultDB]
Not recommended
You can use Await to receive result from the Future and bind it.
Recommended
Use "play.modules.reactivemongo.ReactiveMongoModule"
http://reactivemongo.org/releases/0.12/documentation/tutorial/play.html
Example of configuration:
# The default URI
mongodb.uri = "mongodb://someuser:somepasswd#localhost:27017/foo"
# Another one, named with 'bar'
mongodb.bar.uri = "mongodb://someuser:somepasswd#localhost:27017/lorem"
Example of code
class MyComponent #Inject() (
val defaultApi: ReactiveMongoApi, // corresponds to 'mongodb.uri'
#NamedDatabase("bar") val barApi: ReactiveMongoApi // 'mongodb.bar'
) {
}
If you need to do your own binding, then just look at this example:
https://github.com/ReactiveMongo/Play-ReactiveMongo/blob/master/src/main/scala/play/modules/reactivemongo/ReactiveMongoModule.scala