@webda/core
Version:
Expose API with Lambda
409 lines • 18.7 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { test } from "@testdeck/mocha";
import * as assert from "assert";
import axios from "axios";
import * as fs from "fs";
import * as sinon from "sinon";
import { User, WebdaError } from "../index.js";
import { CoreModel } from "../models/coremodel.js";
import { WebdaTest } from "../test.js";
import { BinaryNotFoundError, BinaryService, LocalBinaryFile, MemoryBinaryFile } from "./binary.js";
import { PassThrough } from "stream";
export class ImageUser extends User {
}
export class TestBinaryService extends BinaryService {
store(object, property, file, metadatas, index) {
throw new Error("Method not implemented.");
}
getUsageCount(hash) {
throw new Error("Method not implemented.");
}
update(object, property, index, file, metadatas) {
throw new Error("Method not implemented.");
}
delete(object, property, index) {
throw new Error("Method not implemented.");
}
challenge(hash, challenge) {
throw new Error("Method not implemented.");
}
_get() {
return null;
}
async cascadeDelete() { }
}
class BinaryTest extends WebdaTest {
getUserStore() {
return this.getService("Users");
}
getBinary() {
return this.getService("binary");
}
getMap() {
return "images";
}
getExposePath() {
return "/binary";
}
getTestFile() {
return process.cwd() + "/test/Dockerfile.txt";
}
async before(init = true) {
this.cleanFiles.push("./downloadTo.tmp");
await super.before(init);
let userStore = this.getUserStore();
let binary = this.getBinary();
assert.notStrictEqual(userStore, undefined);
assert.notStrictEqual(binary, undefined);
await userStore.__clean();
await binary.__clean();
}
async normal() {
let userStore = this.getUserStore();
let binary = this.getBinary();
let map = this.getMap();
let exposePath = this.getExposePath();
var eventFired = 0;
var events = ["Binary.Get", "Binary.Create", "Binary.Delete"];
for (let evt in events) {
binary.on(events[evt], function () {
eventFired++;
});
}
// Check CREATE - READ
var hash;
var user1;
var user2;
var user;
var ctx;
this.log("DEBUG", "Initializing users");
user1 = await userStore.save({ test: "plop" });
user2 = await userStore.save({ test: "plop" });
this.log("DEBUG", "Store a local file");
await binary.store(user1, map, new LocalBinaryFile(this.getTestFile()), {});
user1 = await userStore.get(user1.uuid);
assert.notStrictEqual(user1[map], undefined);
assert.strictEqual(user1[map].length, 1);
hash = user1[map][0].hash;
this.log("DEBUG", "Get usage count");
let value = await binary.getUsageCount(hash);
assert.strictEqual(value, 1);
this.log("DEBUG", "Store same local file on separate object");
await binary.store(user2, map, new LocalBinaryFile(this.getTestFile()), {});
user = await userStore.get(user2.uuid);
assert.notStrictEqual(user[map], undefined);
assert.strictEqual(user[map].length, 1);
assert.strictEqual(user[map][0].constructor.name, "BinaryMap");
hash = user[map][0].hash;
this.log("DEBUG", "Verify usage count");
value = await binary.getUsageCount(hash);
assert.strictEqual(value, 2);
this.log("DEBUG", "Delete one attached object");
await binary.delete(user, map, 0);
user = await userStore.get(user2.uuid);
assert.strictEqual(user[map].length, 0);
this.log("DEBUG", "Verify usage count decreased");
value = await binary.getUsageCount(hash);
assert.strictEqual(value, 1);
// Try to get images on user1 as user2
ctx = await this.newContext({ type: "CRUD", uuid: "PLOP" });
ctx.session.userId = user2.uuid;
let executor = this.getExecutor(ctx, "test.webda.io", "GET", exposePath + "/users/" + user1.uuid + "/" + map + "/0");
this.log("DEBUG", "Verify permission check on HTTP GET");
await assert.rejects(executor.execute(ctx), (err) => err.getResponseCode() === 403);
ctx.session.userId = user1.uuid;
this.log("DEBUG", "Verify valid permission check on HTTP GET");
executor = this.getExecutor(ctx, "test.webda.io", "GET", exposePath + "/users/" + user1.uuid + "/" + map + "/0");
await executor.execute(ctx);
// We dont check for result as FileBinary will return datas and S3 a redirect
if (fs.existsSync("./downloadTo.tmp")) {
fs.unlinkSync("./downloadTo.tmp");
}
this.log("DEBUG", "BinaryMap downloadTo");
await binary.downloadTo(user1[map][0], "./downloadTo.tmp");
// Check the result is the same
assert.strictEqual(fs.readFileSync("./downloadTo.tmp").toString(), fs.readFileSync(this.getTestFile()).toString());
fs.unlinkSync("./downloadTo.tmp");
this.log("DEBUG", "BinaryMap downloadTo from CoreModel");
await user1[map][0].downloadTo("./downloadTo.tmp");
assert.strictEqual(fs.readFileSync("./downloadTo.tmp").toString(), fs.readFileSync(this.getTestFile()).toString());
fs.unlinkSync("./downloadTo.tmp");
// Fake IO issue
let stub = sinon.stub(binary, "_get").callsFake(async (info) => {
const stream = new PassThrough();
// override pipe so that as soon as someone calls .pipe(dest),
// dest will immediately get an "error" event
const origPipe = stream.pipe;
stream.pipe = function (dest) {
// let the normal pipe setup happen first:
origPipe.call(stream, dest);
// then asynchronously emit an error on the destination:
process.nextTick(() => dest.emit("error", new Error("bad read")));
return dest;
};
return stream;
});
let stub2;
try {
this.log("DEBUG", "BinaryMap downloadTo with I/O issues");
await assert.rejects(() => binary.downloadTo(user1[map][0], "./downloadTo.tmp"));
assert.ok(!fs.existsSync("./downloadTo.tmp"));
}
finally {
stub.restore();
if (stub2) {
stub2.restore();
}
}
this.log("DEBUG", "Check get stream");
let buf1 = await BinaryService.streamToBuffer(await user1[map][0].get());
// cov
let buf2 = await BinaryService.streamToBuffer(await binary._get(user1[map][0]));
const buf3 = await user1[map][0].getAsBuffer();
assert.strictEqual(buf1.toString(), buf2.toString());
assert.strictEqual(buf1.toString(), buf3.toString());
this.log("DEBUG", "Delete CoreModel and ensure usage count");
await userStore.delete(user1.uuid);
value = await binary.getUsageCount(hash);
assert.strictEqual(value, 0);
}
async notMapped() {
let userStore = this.getUserStore();
let binary = this.getBinary();
let user1 = await userStore.save({ test: "plop" });
binary.getParameters().models = {};
await assert.rejects(() => binary.store(user1, "images2", new LocalBinaryFile(this.getTestFile()), {}), err => true);
}
async update() {
let userStore = this.getUserStore();
let binary = this.getBinary();
let map = this.getMap();
var user1;
var user;
let hash;
user1 = await userStore.save({ test: "plop" });
await binary.store(user1, map, new LocalBinaryFile(this.getTestFile()), {});
user = await userStore.get(user1.uuid);
assert.notStrictEqual(user[map], undefined);
assert.strictEqual(user[map].length, 1);
let value = await binary.getUsageCount(user[map][0].hash);
assert.strictEqual(value, 1);
}
checkMap() {
let binary = this.getBinary();
binary.handleBinary = () => -1;
// @ts-ignore
assert.throws(() => binary.checkMap(new CoreModel(), "pouf"), /Unknown mapping/);
}
async httpCreate() {
let userStore = this.getUserStore();
let user1 = await userStore.save({ test: "plop" });
let ctx = await this.newContext();
let executor = this.getExecutor(ctx, "test.webda.io", "POST", `/binary/users/${user1.getUuid()}/images`, {});
await assert.rejects(() => executor.execute(ctx), (err) => err.getResponseCode() === 403);
/**
* Two modes should be handled
*
* POST body + X-Webda-Metadatas header
* POST multipart
*/
ctx.getHttpContext().setBody("MY_IMAGE");
ctx.getSession().login(user1.getUuid(), "fake");
// Need to verify
await executor.execute(ctx);
}
async httpDelete() {
let { user1, ctx } = await this.setupDefault(false);
let executor = this.getExecutor(ctx, "test.webda.io", "DELETE", `/binary/users/${user1.getUuid()}/images/0/myhash`, {});
await assert.rejects(() => executor.execute(ctx), WebdaError.Forbidden, "DELETE binary w/o permission");
// Now login
ctx.getSession().login(user1.getUuid(), "fake");
executor = this.getExecutor(ctx, "test.webda.io", "DELETE", `/binary/users/${user1.getUuid()}/images/0/myhash`, {});
await assert.rejects(() => executor.execute(ctx), (err) => err.getResponseCode() === 400);
executor = this.getExecutor(ctx, "test.webda.io", "DELETE", `/binary/users/${user1.getUuid()}/images/0/${user1.images[0].hash}`, {});
await executor.execute(ctx);
await user1.refresh();
assert.ok(user1.images.length === 0);
}
getOperationName() {
let binary = this.getBinary();
// @ts-ignore
assert.strictEqual(binary.getOperationName(), "");
// @ts-ignore
binary._name = "Other";
// @ts-ignore
assert.strictEqual(binary.getOperationName(), binary.getName());
}
async httpGetError() {
let { binary, user1, ctx } = await this.setupDefault();
// @ts-ignore
let stub = sinon.stub(binary, "get").callsFake(() => {
return {
pipe: stream => {
stream.emit("error", new Error("IO UnitTest"));
}
};
});
await this.execute(ctx, "test.webda.io", "GET", `/binary/users/${user1.getUuid()}/images/0`, {});
await assert.rejects(() => this.execute(ctx, "test.webda.io", "GET", ctx.getResponseHeaders().Location.substring("http://test.webda.io".length)), /IO UnitTest/, "GET binary with I/O");
stub.restore();
}
async httpGet() {
let { binary, user1, ctx } = await this.setupDefault(false);
let executor = this.getExecutor(ctx, "test.webda.io", "GET", `/binary/users/${user1.getUuid()}/images/0`, {});
// If not logged in
await assert.rejects(() => executor.execute(ctx), WebdaError.Forbidden, "GET binary w/o permission");
executor = this.getExecutor(ctx, "test.webda.io", "GET", `/binary/users/unknown/images/0`, {});
await assert.rejects(() => executor.execute(ctx), WebdaError.NotFound, "GET binary on unknown object");
// Login
ctx.getSession().login(user1.getUuid(), "fake");
executor = this.getExecutor(ctx, "test.webda.io", "GET", `/binary/users/${user1.getUuid()}/images/0`, {});
await executor.execute(ctx);
await binary.delete(user1, "images", 0);
// Check 404 now it is gone
executor = this.getExecutor(ctx, "test.webda.io", "GET", `/binary/users/${user1.getUuid()}/images/0`, {});
await assert.rejects(() => executor.execute(ctx), (err) => err.getResponseCode() === 404);
}
async httpMetadata() {
var _a;
let { binary, user1, ctx } = await this.setupDefault(false);
let executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/users/${user1.getUuid()}/images/0/${user1.images[0].hash}`, {}); // If not logged in
await assert.rejects(() => executor.execute(ctx), WebdaError.Forbidden, "PUT metadata w/o permission");
executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/users/unknown/images/0/${user1.images[0].hash}`, {});
await assert.rejects(() => executor.execute(ctx), WebdaError.NotFound, "GET binary on unknown object");
// Login
ctx.getSession().login(user1.getUuid(), "fake");
executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/users/${user1.getUuid()}/images/0/${user1.images[0].hash}`, { "my-metadata": "updated", "my-other-metadata": true });
await executor.execute(ctx);
let user = await this.getUserStore().get(ctx.getCurrentUserId());
(_a = user.images[0]).metadata ?? (_a.metadata = {});
assert.strictEqual(user.images[0].metadata["my-metadata"], "updated");
assert.strictEqual(user.images[0].metadata["my-other-metadata"], true);
executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/users/${user1.getUuid()}/images/0/${user1.images[0].hash}`, { "my-metadata": "updated".repeat(4096), "my-other-metadata": true });
await assert.rejects(() => executor.execute(ctx), WebdaError.BadRequest, "PUT metadata with big content should fail");
}
async getNotFound() {
await assert.rejects(() =>
// @ts-ignore
this.getBinary().get({ hash: "none", mimetype: "", size: 10 }), BinaryNotFoundError);
}
async setupDefault(withLogin = true) {
let userStore = this.getUserStore();
let binary = this.getBinary();
let user1 = await userStore.save({ test: "plop" });
await binary.store(user1, "images", new LocalBinaryFile(this.getTestFile()));
await user1.refresh();
let ctx = await this.newContext();
if (withLogin) {
ctx.getSession().login(user1.getUuid(), "fake");
}
user1.setContext(ctx);
return { userStore, binary, user1, ctx };
}
async testChallenge(remoteCheckHash = true) {
let { userStore, binary, user1, ctx } = await this.setupDefault(false);
let { hash, challenge } = await new MemoryBinaryFile(Buffer.from("PLOP"), {}).getHashes();
let metadata = { plop: "test" };
let executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/upload/users/${user1.getUuid()}/images`, {
hash,
challenge,
metadata
});
await assert.rejects(() => executor.execute(ctx), (err) => err.getResponseCode() === 403);
ctx.getSession().login(user1.getUuid(), "fake");
await executor.execute(ctx);
let info = JSON.parse(ctx.getResponseBody());
assert.ok(!info.done, "should not be done yet");
// Execute twice as we should still get an upload
await executor.execute(ctx);
let url = new URL(info.url);
if (url.host === "test.webda.io") {
executor = this.getExecutor(ctx, "test.webda.io", info.method || "PUT", url.pathname + url.search, "PLOP2", info.headers);
await assert.rejects(() => executor.execute(ctx));
executor = this.getExecutor(ctx, "test.webda.io", info.method || "PUT", url.pathname + url.search, "PLOP", info.headers);
await executor.execute(ctx);
}
else {
if (remoteCheckHash) {
await assert.rejects(() => this.sendChallengeData(info, "PLOP2"), "Should check content");
}
await this.sendChallengeData(info, "PLOP");
}
await user1.refresh();
assert.strictEqual(user1.images.length, 2);
assert.strictEqual((await BinaryService.streamToBuffer(await binary.get(user1.images[1]))).toString(), "PLOP");
// If we try to re upload it should be already up
executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/upload/users/${user1.getUuid()}/images`, {
hash,
challenge,
metadata
});
await executor.execute(ctx);
info = JSON.parse(ctx.getResponseBody());
assert.ok(info.done, "should be done");
assert.ok(info.url === undefined);
// Use another user to try adding same binary
let user2 = await userStore.save({});
ctx.getSession().login(user2.getUuid(), "fake");
executor = this.getExecutor(ctx, "test.webda.io", "PUT", `/binary/upload/users/${user2.getUuid()}/images`, {
hash,
challenge,
metadata
});
await executor.execute(ctx);
info = JSON.parse(ctx.getResponseBody());
assert.ok(info.done, "should be done");
assert.ok(info.url === undefined);
}
async sendChallengeData(info, data) {
let res;
try {
res = await axios.request({ method: info.method, url: info.url, data, headers: info.headers });
return res;
}
catch (err) {
console.log("sendChallengeDataError", { method: info.method, url: info.url, data, headers: info.headers }, err);
throw err;
}
}
}
__decorate([
test
], BinaryTest.prototype, "normal", null);
__decorate([
test
], BinaryTest.prototype, "notMapped", null);
__decorate([
test
], BinaryTest.prototype, "update", null);
__decorate([
test
], BinaryTest.prototype, "checkMap", null);
__decorate([
test
], BinaryTest.prototype, "httpCreate", null);
__decorate([
test
], BinaryTest.prototype, "httpDelete", null);
__decorate([
test
], BinaryTest.prototype, "getOperationName", null);
__decorate([
test
], BinaryTest.prototype, "httpGetError", null);
__decorate([
test
], BinaryTest.prototype, "httpGet", null);
__decorate([
test
], BinaryTest.prototype, "httpMetadata", null);
__decorate([
test
], BinaryTest.prototype, "getNotFound", null);
export { BinaryTest };
//# sourceMappingURL=binary.spec.js.map