How to create an ArrayBuffer Variable in Angular unit test, Jasmine/Karma - javascript

Hi i have a function that downloads an excel file coming from backend
component.ts
getExcelExport(resultsFilterRootObject: ResultsFilterRootObject) {
return this.http.post(urls.getExcelExportCPPMetrics , resultsFilterRootObject, {
responseType: 'arraybuffer',
observe: 'response'
})
.pipe(
tap(
data => {
const blob = new Blob([data.body], {type: 'application/vnd.ms-excel'});
const filename = 'vehicle-metrics-template.xls';
FileSaver.saveAs(blob, filename);
},
catchError(MetricsService.handleError)
)
);
}
component.spec.ts
it('should download Excel ', () => {
// const expectedResult: ArrayBuffer = new ArrayBuffer(8); Tried this fails too
const expectedResult = new TextEncoder();
expectedResult.encode("This is a string converted to a Uint8Array");
httpClientSpy.post.and.returnValue(asyncData(expectedResult));
metricsService.getExcelExportCPPMetrics(resultsFilterRootObject).subscribe(
heroes => expect(heroes).toEqual(expectedResult, 'expected VehicleSalesResultRootObject'),
fail
);
expect(httpClientSpy.post.calls.count()).toBe(1, 'one call');
});
I keep getting error error TS2345: Argument of type 'TextEncoder' is not assignable to parameter of type 'Expected<HttpResponse<ArrayBuffer>>'.
Type 'TextEncoder' is missing the following properties from type 'ObjectContaining<HttpResponse<ArrayBuffer>>': jasmineMatches, jasmineToString
Basically if I can create a variable of type ArrayBuffer in the Unit
Test this problem would be solved
any idea on this ?

Note that post method with params responseType: 'arraybuffer' and observe: 'response' returns value Observable<HttpResponse<ArrayBuffer>> which is not directly ArrayBuffer as provided there:
post(url: string, body: any | null, options: {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
observe: 'response';
params?: HttpParams | {
[param: string]: string | string[];
};
reportProgress?: boolean;
responseType: 'arraybuffer';
withCredentials?: boolean;
}): Observable<HttpResponse<ArrayBuffer>>;
What you can do is return Observable with simple object that has property which are you using - body:
it('should download Excel ', () => {
const expectedResult: ArrayBuffer = new ArrayBuffer(8);
// httpClientSpy.post.and.returnValue(asyncData(expectedResult));
httpClientSpy.post.and.returnValue(of({body: expectedResult})); // Or that below one if "asyncData" return Observable
metricsService.getExcelExportCPPMetrics(resultsFilterRootObject).subscribe(
data => expect(data.body).toEqual(expectedResult, 'expected VehicleSalesResultRootObject'),
fail
);
expect(httpClientSpy.post.calls.count()).toBe(1, 'one call');
});

Related

base64 data video upload to firebase storage failure

I have the following code (please ignore it's ugliness):
const convertToVideoBlob = async (dataURL : string, storageRef: StorageReference) : Promise<string> => {
console.log("dataURL: ", dataURL)
const promiseRes : any = await fetch(dataURL);
const blob : Blob = (await promiseRes.blob());
const reader = new FileReader();
reader.onloadend = async ()=>{
const newDataURL : string | ArrayBuffer = reader.result || ''
console.log(`testing: ${newDataURL}`);
console.log(`testing type: ${typeof(newDataURL)}`);
await uploadString(storageRef, (newDataURL as string), 'base64');
const videoStorageDownloadURL: string = await getDownloadURL(storageRef);
console.log("videoStorageDLlink: ", videoStorageDownloadURL)
return videoStorageDownloadURL;
}
// console.log("outside newDataURL: ", newDataURL);
reader.readAsDataURL(blob);
return 'notAvailable'
}
where dataURL is the following string, representing a screen recording my React typescript application recorded:
blob:https://www.junojourney.com/a40cbd71-ea64-4bf9-b563-c6dcb34bdf53
in order to upload to firebase, I needed to transfer it into a Blob object and from there to to use reader.readAsDataURL(blob) which provides me of the following result within newDataURL:
data:video/mp4;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQRChYECGFOAZwH/////////FUmpZpkq17GDD0JAT....
which is from the required form of data presentation in order to use after the uploadString() method for uploading to firebase, as far as i'm aware..
but, weirdly i'm getting the following error:
content.js:205 Uncaught (in promise) FirebaseError: Firebase Storage: String does not match format 'base64': Invalid character found (storage/invalid-format)
and if I change the line:
await uploadString(storageRef, (newDataURL as string), 'base64');
to
await uploadString(storageRef, newDataURL, 'base64');
I get the following error:
const newDataURL: string | ArrayBuffer
Argument of type 'string | ArrayBuffer' is not assignable to parameter of type 'string'.
Type 'ArrayBuffer' is not assignable to type 'string'.ts(2345)
Any ideas? Regards!

Storing file in PostgreSQL database using NestJS

I have one form in React. It has many fields. once user click on the submit button all the field should be saved in database. It also contains one file attachment(pdf).
I dont know what the datatype of variable which will store file, I should take in entity class. Also what should be the database column type. I am using TypeORM for the same.
#IsNotEmpty()
#IsDate()
endDate: Date;
#Column()
#IsNotEmpty()
#IsString()
personIncharge: string;
#Column()
#IsNotEmpty()
#IsString()
country: string;
#Column()
#IsNotEmpty()
#IsString()
comments: string;
attachFile: string; // Should I take file or string?
You will probably find your solution in this StackOverflow comment
Basically, you turn your column type in a blob or longblob in your TypeORM annotation, and then use the type Buffer in your Entity's field
#Column({type: 'longblob'})
attachFile: Buffer;
Then you will be able to serve the file as showed in the post example:
app.get("/file/:id", async (req, res)=>{
try {
const repo = getConnection().getRepository(MYFile)
const result_find = await repo.findOne(req.params.id)
console.log(result_find);
var fileData = result_find.data;
res.writeHead(200, {
'Content-Type': result_find.mimeType,
'Content-Disposition': 'attachment; filename=' + result_find.name,
'Content-Length': fileData.length
});
res.write(fileData);
res.end();
} catch (error) {
console.log(error)
res.send("ERROR")
}
})
if you want using string, client must send base64 file to backend.
format: data:(mimetype);(charset),(encoded) -> data:image/png;base64,\ee\9f920d....
here solution, using base64 string
DTO (data transfer object)
import { IsDefined, IsNotEmpty } from 'class-validator';
export class UpdateUserAvatarDto {
#IsDefined()
#IsNotEmpty()
file: string;
}
controller
#UseGuards(JwtAuthGuard)
#Patch('account/avatar')
async updateAvatar(
#User() user: Payload,
#Body() updateUserAvatarDto: UpdateUserAvatarDto,
#Res() res: Response,
) {
try {
const { file } = updateUserAvatarDto;
createFile(file, { prefix: ['user', 'avatar'], name: user.id }); // file is string base64 you can store it to database.
return response(res, HttpStatus.OK, {
message: 'Successfully update avatar',
});
} catch (e) {
console.error(e);
return response(res, HttpStatus.INTERNAL_SERVER_ERROR, {
message: e,
data: null,
});
}
}
if you want to create a file from base64
export const createFile = async (base64, { prefix, name }) => {
const cleanBase64 = base64.split(',')[1];
const buffer = Buffer.from(cleanBase64, 'base64');
const file = await fileType.fromBuffer(buffer);
return fs.writeFileSync(
path.join(
path.resolve('./'),
...['public', ...prefix, `${name}.${file.ext}`],
),
buffer,
);
};

Busboy finish not fired

I want to pass original file stream pass down to other layer of code which will handle later drop on disk (upload to cloud storage) behavior. As files size might be large I can't actually fully buffer incoming file. I assume that PassThrough stream should pass needed data. While file.resume already called, finish event never get called.
How can I collect all required form fields along with single file stream and make proper service call, without explicit whole file in memory storage or on local disk, as I have a few of both of them?
private collectMultipartRequest (req: Request, fileFieldName: string): Promise<{ file: IFile, fields: { [k: string]: string }}> {
const obj = {
file: null,
fields: {}
};
return new Promise ((resolve, reject) => {
const busboy = new Busboy({ headers: req.headers, limits: { files: 1 }});
busboy.on("file", (fieldname, file, filename, mimetype) => {
if (fieldname === fileFieldName) {
const passThrough = new PassThrough();
file.pipe(passThrough);
obj.file = <IFile>{
mimeType: mimetype,
name: filename,
readStream: passThrough
};
}
file.resume();
});
busboy.on("field", (fieldName, val) => {
obj.fields[fieldName] = val;
});
busboy.on("filesLimit", () => {
reject(obj);
});
busboy.on("finish", async () => {
resolve(obj);
});
req.pipe(busboy);
});
}

how to add headers to form data in angular 5

How do I add headers to form data that has a progress bar?
I get the following error:
ERROR in src/app/services/auth.service.ts(91,23): error TS2554:
Expected 2-4 arguments, but got 5.
Code:
public upload(
files: Set<File>
): { [key: string]: { progress: Observable<number> } } {
// this will be the our resulting map
const status: { [key: string]: { progress: Observable<number> } } = {};
files.forEach(file => {
// create a new multipart-form for every file
const formData: FormData = new FormData();
formData.append('file', file, file.name);
// formData.append('name', course, course.name);
// formData.append('text', username, username.name);
let headers = new Headers();
this.loadToken();
headers.append('Authorization', this.authToken);
// headers.append('Content-type', undefined);
// create a http-post request and pass the form
// tell it to report the upload progress
const req = new HttpRequest('POST', 'users/upload', formData,{headers: headers},{
reportProgress: true
});
// create a new progress-subject for every file
const progress = new Subject<any>();
// send the http-request and subscribe for progress-updates
const startTime = new Date().getTime();
this.https.request(req).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
// calculate the progress percentage
const percentDone = Math.round((100 * event.loaded) / event.total);
// pass the percentage into the progress-stream
progress.next(percentDone);
} else if (event instanceof HttpResponse) {
// Close the progress-stream if we get an answer form the API
// The upload is complete
progress.complete();
}
});
// Save every progress-observable in a map of all observables
status[file.name] = {
progress: progress.asObservable()
};
});
// return the map of progress.observables
return status;
}
ERROR in src/app/services/auth.service.ts(91,23): error TS2554:
Expected 2-4 arguments, but got 5.
The error message says that it expected 2-4 arguments, but got 5.
headers and reportProgress shouldn't be separate arguments, they both should be part of the fourth argument in HttpRequest.
To fix the error, change the HttpRequest as shown below:
const req = new HttpRequest('POST', 'users/upload', formData,
{ headers: headers, reportProgress: true });

Angular HttpClient, setting map type dynamically

I have a method on an service to handle all backend requests. Instead of writing a whole bunch of different calls using the HttpClient, I thought I might write one single function that could connect to my backend and pass it arguments to handle different types of data.
Consider this function
public postRequest(token: string, url: string, body: any, headers: Object = {}) : Observable<any> {
//create new header object
const httpOptions = {
headers: new HttpHeaders()
.set('Authorization', token)
};
//add the headers if any
for(let index in headers){
httpOptions.headers.set(index, headers[index]);
}
//connect to the backend and return the repsonse
return this.http.post( this.config.BASE_SERVER_URL + url, body , httpOptions)
.pipe(
map((res) => {
return res;
}),
catchError(this.handleError)
);
}
It works well except I wanted to be able to set the response type dynamically. Thus I could set the method to use one of my model types.
Here's what I'm trying to accomplish. Hopefully this makes sense.
map(res: "Attendee") => {}
//or
map(res: typeof(typeInput)) => {}
Is it possible to pas a "dynamic" type to the http map method so I can map the different responses to a model of my choice?
I can achieve this by using generic methods.
you can use this approach.
my-own.service.ts
userAuthentication<T>(userName: string, password: string): Observable<T> {
const url = `http://my-own.url`;
const targetData = {
'emailId': userName,
'password': password
};
return this.http.post<CommonResponse<T>>(url, targetData, httpOptions).pipe(
retry(3),
map((data: CommonResponse<T>) => {
if (data.status) {
if (!data.result[0]) {
this.showMessage('You are not authorized for login');
return null;
}
return data.result[0] as T;
}
this.showMessage(data.message);
return null;
}),
tap((userProfile: T) => {
console.log('UserLogin ');
}),
catchError(this.handleError<T>('unable to logged in')));
}
CommonResponse model
export class CommonResponse<T> {
autherizationExpires: string;
autherizationKey: string;
message: string;
result: T | T[];
status: boolean;
}
So, when you call this method like myOwnService.userAuthentication < LoginModel >(...params).subscribe(/ * your codes * /);
It will inherited to the map as well.
let me know if I am not get your question.

Categories