I have a REST endpoint that generates random images. I'm using Spring REST Docs, but the Response is all garbled in the http-response.adoc file. Is there an easy way for Mock MVC and REST Docs to store the file somewhere so my .adoc files can reference it?
Not the perfect but working solution:
class ImageSnippet implements Snippet {
private final String filePath;
public ImageSnippet(String filePath) {
this.filePath = filePath;
}
#Override
public void document(Operation operation) throws IOException {
byte[] picture = operation.getResponse().getContent();
Path path = Paths.get(filePath);
Files.deleteIfExists(path);
Files.createDirectories(path.getParent());
Files.createFile(path);
try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
fos.write(picture);
}
}
}
And usage in MockMvc test (image folder path is important):
mockMvc.perform(get("/my-profile/barcode")
.accept(MediaType.IMAGE_PNG))
.andExpect(status().isOk())
.andDo(document("my-profile/barcode",
new ImageSnippet("build/asciidoc/html5/images/barcode.png")));
In .adoc template:
image::barcode.png[]
And here is my build.gradle Asciidoctor configuration (imagesdir is important):
asciidoctor {
dependsOn test
backends = ['html5']
options doctype: 'book'
attributes = [
'source-highlighter': 'highlightjs',
'imagesdir' : './images',
'toc' : 'left',
'toclevels' : 3,
'numbered' : '',
'icons' : 'font',
'setanchors' : '',
'idprefix' : '',
'idseparator' : '-',
'docinfo1' : '',
'safe-mode-unsafe' : '',
'allow-uri-read' : '',
'snippets' : snippetsDir,
linkattrs : true,
encoding : 'utf-8'
]
inputs.dir snippetsDir
outputDir 'build/asciidoc'
sourceDir 'src/docs/asciidoc'
sources {
include 'index.adoc'
}
}
What you can do is implement a custom Snippet which saves the resulting response. You can use the RestDocumentationContext attribute of the operation you receive to obtain the output directory.
mockMvc.perform(get("/example"))
.andDo(document("some-example", operation -> {
var context = (RestDocumentationContext) operation.getAttributes().get(RestDocumentationContext.class.getName());
var path = Paths.get(context.getOutputDirectory().getAbsolutePath(), operation.getName(), "response-file.png");
Files.createDirectories(path.getParent());
Files.write(path, operation.getResponse().getContent());
}));
However this will create a .png file in your output directory which generally isn't really useful if you have Asciidoc in a source directory that needs to embed it. So what you can instead do is create an Asciidoc file which contains custom HTML of an image tag with its source being a base64 representation of the response.
mockMvc.perform(get("/example"))
.andDo(document("some-example", operation -> {
var context = (RestDocumentationContext) operation.getAttributes().get(RestDocumentationContext.class.getName());
var path = Paths.get(context.getOutputDirectory().getAbsolutePath(), operation.getName(), "response-file.adoc");
var outputStream = new ByteArrayOutputStream();
outputStream.write("++++\n".getBytes());
outputStream.write("<img src=\"data:image/png;base64,".getBytes());
outputStream.write(Base64.getEncoder().encode(operation.getResponse().getContent()));
outputStream.write("\"/>\n".getBytes());
outputStream.write("++++\n".getBytes());
Files.createDirectories(path.getParent());
Files.write(path, outputStream.toByteArray());
}));
Although a bit more overhead in terms of space, if you use this, you don't need to mess with referencing build files from your sources.
An alternative way if you don't need/want to show the images in the docs: Use a ContentModifyingOperationPreprocessor to replace the bytes with some string that makes it clear to the readers of the documentation that there will be some image bytes in the response.
For example:
mockMvc.perform(get("/api/users/{id}/avatar", user.getId().asString())
.with(createCustomerAuth()))
.andExpect(status().isOk())
.andDo(document("get-user-avatar-example",
null,
Preprocessors.preprocessResponse(new ContentModifyingOperationPreprocessor(new ContentModifier() {
#Override
public byte[] modifyContent(byte[] originalContent, MediaType contentType) {
return "<< IMAGE BODY HERE >>".getBytes(StandardCharsets.UTF_8);
}
}))));
This generates an adoc file like this:
[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 15
Cache-Control: max-age=3600
<< IMAGE BODY HERE >>
----
Related
I have a simple API function to upload a file similar to:
#PostMapping(value = "/documents",
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public Mono<ResponseEntity<String>> uploadDocument(#RequestPart Mono<FilePart> file){
return storeDocumentService
.upload(file)
.map(fileLocation->ResponseEntity.ok(fileLocation))
}
The code works ok and uploads the file. The problem comes when I want to make the response a bit better by returning the link to the uploaded file. For this I want to use HATEOAS 'org.springframework.boot:spring-boot-starter-hateoas'. As soon as I add the dependency 'org.springframework.boot:spring-boot-starter-hateoas' to my 'build.gradle' the endpoint stops working and I get a response:
{
"timestamp": "2023-02-20T04:28:10.620+00:00",
"status": 415,
"error": "Unsupported Media Type",
"path": "/documents"
}
and also I get in the logs:
2023-02-20T05:28:10.618+01:00 WARN 2993 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/pdf' is not supported]
It is important to point out that I upload a ".pdf" file with a header "Content-Type:multipart/form-data". And most important the only change in the working code and not working code is that i just add the dependency for HATEOAS 'org.springframework.boot:spring-boot-starter-hateoas'
For Uploading File We can easily use the type MultiPartFile , This will handles all the types of files and we can easily retrive the fileInputStream(data) from it.
The following code may helps you!..
#PostMapping("uploadExcelData")
public ResponseEntity<?> uploadExcelData(#RequestParam MultipartFile file) throws IOException {
List<...> dataList = fileHandling.convertFileAsJson(file);
if (!dataList.isEmpty()) {
return ....
} else {
return ResponseEntity.ok("No Records found !!");
}
}
I hope the above code will helps you to handle the File in the Endpoint.
I need to get absolute path to current active configuration file in Spring boot that don't locate in classpath or resources
It can be located in default place - project folder, subfolder "config", set via spring.config.location and in random place, also in another disk
Something like "E:\projects\configs\myProject\application.yml"
Assume that you have these application-{env}.yml config profiles in resources folder, and we are going to activate the dev configuration.
application.yml
application-dev.yml
application-prod.yml
application-test.yml
...
There are two ways you can activate the dev :
modify your application.yml,
spring:
profiles:
active: dev
or by command line when you want to start your application :
java -jar -Dspring.profiles.active=dev application.jar
Then, try this code in your program:
// get the active config dynamically
#Value("${spring.profiles.active}")
private String activeProfile;
public String readActiveProfilePath() {
try {
URL res = getClass().getClassLoader().getResource(String.format("application-%s.yml", activeProfile));
if (res == null) {
res = getClass().getClassLoader().getResource("application.yml");
}
File file = Paths.get(res.toURI()).toFile();
return file.getAbsolutePath();
} catch (Exception e) {
// log the error.
return "";
}
}
The output will be an absolute path of application-dev.yml
Someday I found same question here, but cant find it now
So here my solution, maybe someone needs it
#Autowired
private ConfigurableEnvironment env;
private String getYamlPath() throws UnsupportedEncodingException {
String projectPath = System.getProperty("user.dir");
String decodedPath = URLDecoder.decode(projectPath, "UTF-8");
//Get all properies
MutablePropertySources propertySources = env.getPropertySources();
String result = null;
for (PropertySource<?> source : propertySources) {
String sourceName = source.getName();
//If configuration loaded we can find properties in environment with name like
//"Config resource '...[absolute or relative path]' via ... 'path'"
//If path not in classpath -> take path in brackets [] and build absolute path
if (sourceName.contains("Config resource 'file") && !sourceName.contains("classpath")) {
String filePath = sourceName.substring(sourceName.indexOf("[") + 1, sourceName.indexOf("]"));
if (Paths.get(filePath).isAbsolute()) {
result = filePath;
} else {
result = decodedPath + File.separator + filePath;
}
break;
}
}
//If configuration not loaded - return default path
return result == null ? decodedPath + File.separator + YAML_NAME : result;
}
Not the best solution I suppose but it works
If you have any idea how to improve it I would really appreciate it
I installed nightwatch-vrt locally in my project. Npm showed me multiple vulnerities which I ignored.
I created a nightwatch.vrt.conf.js with the following content:
const path = require('path');
const baseConfig = require('./nightwatch.conf.js');
const config = {
...baseConfig,
custom_commands_path: ['node_modules/nightwatch-vrt/commands'],
custom_assertions_path: ['node_modules/nightwatch-vrt/assertions']
};
function generateScreenshotFilePath(nightwatchClient, basePath, fileName) {
const moduleName = nightwatchClient.currentTest.module,
testName = nightwatchClient.currentTest.name;
return path.join(process.cwd(), basePath, moduleName, testName, fileName);
};
config.test_settings.default.globals = {
"visual_regression_settings": {
"generate_screenshot_path": generateScreenshotFilePath,
"latest_screenshots_path": "vrt/latest",
"latest_suffix": "",
"baseline_screenshots_path": "vrt/baseline",
"baseline_suffix": "",
"diff_screenshots_path": "vrt/diff",
"diff_suffix": "",
"threshold": 0.5,
"prompt": false,
"always_save_diff_screenshot": true
}
}
module.exports = config;
My test (simple, just to see if it works) looks like:
module.exports = {
tags: ['x'],
'visual testing':function(browser) {
browser
.url('https://www.kraeuter-und-duftpflanzen.de')
.maximizeWindow()
.assert.visible('.header-main')
.pause(1000)
.assert.screenshotIdenticalToBaseline('.header-main')
//.saveScreenshot('./tests_output/image.png')
.end();
}
}
Now the test passes, no assertions failed, a folder is created and the file is correctly named placed there, but I can only see an field with checkerboard pattern (like the transparent background in vector graphics) in the size of the captured element.
Before the test report messages like this are shown:
[32644:26476:0414/082519.134:ERROR:device_event_log_impl.cc(214)] [08:25:19.134]
USB: usb_device_handle_win.cc:1049 Failed to read descriptor from node connection:
Ein an das System angeschlossenes Gerõt funktioniert nicht. (0x1F)
If I let Nightwatch take a screenshot itself, it is displayed correctly.
Does anyone know, where's the mistake?
it seems that this package is broken and not updated for a very very long time. I advise you to update to this one https://www.npmjs.com/package/#bbc/nightwatch-vrt
I'm building a microservice using Spring Boot + Webflux, and I have an endpoint that accepts a multipart file upload. Which is working fine when I test with curl and Postman
#PostMapping("/upload", consumes = [MULTIPART_FORM_DATA_VALUE])
fun uploadVideo(#RequestPart("video") filePart: Mono<FilePart>): Mono<UploadResult> {
log.info("Video upload request received")
return videoFilePart.flatMap { video ->
val fileName = video.filename()
log.info("Saving video to tmp directory: $fileName")
val file = temporaryFilePath(fileName).toFile()
video.transferTo(file)
.thenReturn(UploadResult(true))
.doOnError { error ->
log.error("Failed to save video to temporary directory", error)
}
.onErrorMap {
VideoUploadException("Failed to save video to temporary directory")
}
}
}
I'm now trying to test using WebTestClient:
#Test
fun shouldSuccessfullyUploadVideo() {
client.post()
.uri("/video/upload")
.contentType(MULTIPART_FORM_DATA)
.syncBody(generateBody())
.exchange()
.expectStatus()
.is2xxSuccessful
}
private fun generateBody(): MultiValueMap<String, HttpEntity<*>> {
val builder = MultipartBodyBuilder()
builder.part("video", ClassPathResource("/videos/sunset.mp4"))
return builder.build()
}
The endpoint is returning a 500 because I haven't created the temp directory location to write the files to. However the test is passing even though I'm checking for is2xxSuccessful if I debug into the assertion that is2xxSuccessful performs, I can see it's failing because of the 500, however I'm still getting a green test
Not sure what I am doing wrong here. The VideoUploadException that I map to simply extends ResponseStatusException
class VideoUploadException(reason: String) : ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, reason)
I am using Spring boot upload files to server.I found something weird.when I upload a Jpg file with 4.88MB ,it takes 8.47s,but a 3.16M zip file,it takes about 1.3min. Considering this compressed file contains over 900 small files. I changed another zip file contains 10 small files.Now it takes 33.96s to transfer data even this new zip file is 6.52M,more lager then the old.
So does the number of files in the zip file affect the upload speed? If so, why?
application.yml:
spring:
http:
multipart:
enabled: true
max-file-size: 20MB
java:
#RequestMapping("/changeMainAttachment")
#ResponseBody
public Object addChangeMainAttachment(#RequestParam("changeMainAttachment[]") MultipartFile[] files){
String fileSavePath = gunsProperties.getFileUploadPath();
for (MultipartFile multipartFile : files) {
String fileName = UUID.randomUUID() + "#_#_#" + multipartFile.getOriginalFilename();
try {
logger.info("file uploaded to: " + fileSavePath + fileName);
multipartFile.transferTo(new File(fileSavePath + fileName));
} catch (Exception e) {
throw new BusinessException(BizExceptionEnum.UPLOAD_ERROR);
}
}
return new Msg();
}
At the front side,I use Bootstrap File-input
js:
initFileInput: function(select,_url,_showZone,_async,befordUpload,afterUploaded){
var i = $(select).fileinput({
theme: "gly",
uploadUrl: _url,
dropZoneEnabled: _showZone,
uploadAsync: _async,
showUpload: true,
showUploadedThumbs: true,
maxFileSize: 20*1024,
maxFileCount: 5,
msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!",
hideThumbnailContent: true // hide image, pdf, text or other content in the thumbnail preview
});
if(befordUpload && typeof befordUpload == 'function'){
i.on('filebatchpreupload',befordUpload);
}
if(afterUploaded && typeof afterUploaded == 'function'){
i.on('filebatchuploadcomplete',afterUploaded);
}
}
I call this method by the following way
ChangeAdd.initFileInput("#changeMainAttachment", /change/changeMainAttachment", true, true, ChangeAdd.fileBeforeUpload, ChangeAdd.fileAfterUploaded);