var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { ArrayUtility } from '../../src/utils/array.js';
import { DidKeyResolver } from '../../src/did/did-key-resolver.js';
import { Message } from '../../src/core/message.js';
import { RecordsDeleteHandler } from '../../src/handlers/records-delete.js';
import { sleep } from '../../src/utils/time.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import { DataStream, DidResolver, Dwn, Encoder, Jws, RecordsDelete, RecordsRead, RecordsWrite } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testRecordsDeleteHandler() {
    describe('RecordsDeleteHandler.handle()', () => {
        if (process.env.TESTSOFF === 'true') {
            return;
        }
        let didResolver;
        let messageStore;
        let dataStore;
        let eventLog;
        let dwn;
        describe('functional tests', () => {
            // important to follow the `before` and `after` pattern to initialize and clean the stores in tests
            // so that different test suites can reuse the same backend store for testing
            before(() => __awaiter(this, void 0, void 0, function* () {
                didResolver = new DidResolver([new DidKeyResolver()]);
                const stores = TestStores.get();
                messageStore = stores.messageStore;
                dataStore = stores.dataStore;
                eventLog = stores.eventLog;
                dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog });
            }));
            beforeEach(() => __awaiter(this, void 0, void 0, function* () {
                sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
                // clean up before each test rather than after so that a test does not depend on other tests to do the clean up
                yield messageStore.clear();
                yield dataStore.clear();
                yield eventLog.clear();
            }));
            after(() => __awaiter(this, void 0, void 0, function* () {
                yield dwn.close();
            }));
            it('should handle RecordsDelete successfully and return 404 if deleting a deleted record', () => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                const alice = yield DidKeyResolver.generate();
                // insert data
                const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
                const writeReply = yield dwn.processMessage(alice.did, message, dataStream);
                expect(writeReply.status.code).to.equal(202);
                // ensure data is inserted
                const queryData = yield TestDataGenerator.generateRecordsQuery({
                    author: alice,
                    filter: { recordId: message.recordId }
                });
                const reply = yield dwn.processMessage(alice.did, queryData.message);
                expect(reply.status.code).to.equal(200);
                expect((_a = reply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1);
                // testing delete
                const recordsDelete = yield RecordsDelete.create({
                    recordId: message.recordId,
                    authorizationSignatureInput: Jws.createSignatureInput(alice)
                });
                const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
                expect(deleteReply.status.code).to.equal(202);
                // ensure a query will no longer find the deleted record
                const reply2 = yield dwn.processMessage(alice.did, queryData.message);
                expect(reply2.status.code).to.equal(200);
                expect((_b = reply2.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(0);
                // testing deleting a deleted record
                const recordsDelete2 = yield RecordsDelete.create({
                    recordId: message.recordId,
                    authorizationSignatureInput: Jws.createSignatureInput(alice)
                });
                const recordsDelete2Reply = yield dwn.processMessage(alice.did, recordsDelete2.message);
                expect(recordsDelete2Reply.status.code).to.equal(404);
            }));
            it('should not affect other records or tenants with the same data', () => __awaiter(this, void 0, void 0, function* () {
                const alice = yield DidKeyResolver.generate();
                const bob = yield DidKeyResolver.generate();
                const data = Encoder.stringToBytes('test');
                // alice writes a records with data
                const aliceWriteData = yield TestDataGenerator.generateRecordsWrite({ author: alice, data });
                const aliceWriteReply = yield dwn.processMessage(alice.did, aliceWriteData.message, aliceWriteData.dataStream);
                expect(aliceWriteReply.status.code).to.equal(202);
                // alice writes another record with the same data
                const aliceAssociateData = yield TestDataGenerator.generateRecordsWrite({ author: alice, data });
                const aliceAssociateReply = yield dwn.processMessage(alice.did, aliceAssociateData.message, aliceAssociateData.dataStream);
                expect(aliceAssociateReply.status.code).to.equal(202);
                // bob writes a records with same data
                const bobWriteData = yield TestDataGenerator.generateRecordsWrite({ author: bob, data });
                const bobWriteReply = yield dwn.processMessage(bob.did, bobWriteData.message, bobWriteData.dataStream);
                expect(bobWriteReply.status.code).to.equal(202);
                // bob writes another record with the same data
                const bobAssociateData = yield TestDataGenerator.generateRecordsWrite({ author: bob, data });
                const bobAssociateReply = yield dwn.processMessage(bob.did, bobAssociateData.message, bobAssociateData.dataStream);
                expect(bobAssociateReply.status.code).to.equal(202);
                // alice deletes one of the two records
                const aliceDeleteWriteData = yield TestDataGenerator.generateRecordsDelete({
                    author: alice,
                    recordId: aliceWriteData.message.recordId
                });
                const aliceDeleteWriteReply = yield dwn.processMessage(alice.did, aliceDeleteWriteData.message);
                expect(aliceDeleteWriteReply.status.code).to.equal(202);
                // verify the other record with the same data is unaffected
                const aliceRead1 = yield RecordsRead.create({
                    recordId: aliceAssociateData.message.recordId,
                    authorizationSignatureInput: Jws.createSignatureInput(alice)
                });
                const aliceRead1Reply = yield dwn.handleRecordsRead(alice.did, aliceRead1.message);
                expect(aliceRead1Reply.status.code).to.equal(200);
                const aliceDataFetched = yield DataStream.toBytes(aliceRead1Reply.record.data);
                expect(ArrayUtility.byteArraysEqual(aliceDataFetched, data)).to.be.true;
                // alice deletes the other record
                const aliceDeleteAssociateData = yield TestDataGenerator.generateRecordsDelete({
                    author: alice,
                    recordId: aliceAssociateData.message.recordId
                });
                const aliceDeleteAssociateReply = yield dwn.processMessage(alice.did, aliceDeleteAssociateData.message);
                expect(aliceDeleteAssociateReply.status.code).to.equal(202);
                // verify that alice can no longer fetch the 2nd record
                const aliceRead2Reply = yield dwn.handleRecordsRead(alice.did, aliceRead1.message);
                expect(aliceRead2Reply.status.code).to.equal(404);
                // verify that bob can still fetch record with the same data
                const bobRead1 = yield RecordsRead.create({
                    recordId: bobAssociateData.message.recordId,
                    authorizationSignatureInput: Jws.createSignatureInput(bob)
                });
                const bobRead1Reply = yield dwn.handleRecordsRead(bob.did, bobRead1.message);
                expect(bobRead1Reply.status.code).to.equal(200);
                const bobDataFetched = yield DataStream.toBytes(bobRead1Reply.record.data);
                expect(ArrayUtility.byteArraysEqual(bobDataFetched, data)).to.be.true;
            }));
            it('should return 404 if deleting a non-existent record', () => __awaiter(this, void 0, void 0, function* () {
                const alice = yield DidKeyResolver.generate();
                // testing deleting a non-existent record
                const recordsDelete = yield RecordsDelete.create({
                    recordId: 'nonExistentRecordId',
                    authorizationSignatureInput: Jws.createSignatureInput(alice)
                });
                const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
                expect(deleteReply.status.code).to.equal(404);
            }));
            it('should be disallowed if there is a newer RecordsWrite already in the DWN ', () => __awaiter(this, void 0, void 0, function* () {
                var _c;
                const alice = yield DidKeyResolver.generate();
                // initial write
                const initialWriteData = yield TestDataGenerator.generateRecordsWrite({ author: alice });
                const initialWriteReply = yield dwn.processMessage(alice.did, initialWriteData.message, initialWriteData.dataStream);
                expect(initialWriteReply.status.code).to.equal(202);
                // generate subsequent write and delete with the delete having an earlier timestamp
                // NOTE: creating RecordsDelete first ensures it has an earlier `messageTimestamp` time
                const recordsDelete = yield RecordsDelete.create({
                    recordId: initialWriteData.message.recordId,
                    authorizationSignatureInput: Jws.createSignatureInput(alice)
                });
                yield sleep(1);
                const subsequentWriteData = yield TestDataGenerator.generateFromRecordsWrite({
                    existingWrite: initialWriteData.recordsWrite,
                    author: alice
                });
                // subsequent write
                const subsequentWriteReply = yield dwn.processMessage(alice.did, subsequentWriteData.message, subsequentWriteData.dataStream);
                expect(subsequentWriteReply.status.code).to.equal(202);
                // test that a delete with an earlier `messageTimestamp` results in a 409
                const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
                expect(deleteReply.status.code).to.equal(409);
                // ensure data still exists
                const queryData = yield TestDataGenerator.generateRecordsQuery({
                    author: alice,
                    filter: { recordId: initialWriteData.message.recordId }
                });
                const expectedEncodedData = Encoder.bytesToBase64Url(subsequentWriteData.dataBytes);
                const reply = yield dwn.processMessage(alice.did, queryData.message);
                expect(reply.status.code).to.equal(200);
                expect((_c = reply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(1);
                expect(reply.entries[0].encodedData).to.equal(expectedEncodedData);
            }));
            it('should be able to delete then rewrite the same data', () => __awaiter(this, void 0, void 0, function* () {
                var _d, _e, _f;
                const alice = yield DidKeyResolver.generate();
                const data = Encoder.stringToBytes('test');
                const encodedData = Encoder.bytesToBase64Url(data);
                // alice writes a record
                const aliceWriteData = yield TestDataGenerator.generateRecordsWrite({
                    author: alice,
                    data
                });
                const aliceWriteReply = yield dwn.processMessage(alice.did, aliceWriteData.message, aliceWriteData.dataStream);
                expect(aliceWriteReply.status.code).to.equal(202);
                const aliceQueryWriteAfterAliceWriteData = yield TestDataGenerator.generateRecordsQuery({
                    author: alice,
                    filter: { recordId: aliceWriteData.message.recordId }
                });
                const aliceQueryWriteAfterAliceWriteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceWriteData.message);
                expect(aliceQueryWriteAfterAliceWriteReply.status.code).to.equal(200);
                expect((_d = aliceQueryWriteAfterAliceWriteReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(1);
                expect(aliceQueryWriteAfterAliceWriteReply.entries[0].encodedData).to.equal(encodedData);
                // alice deleting the record
                const aliceDeleteWriteData = yield TestDataGenerator.generateRecordsDelete({
                    author: alice,
                    recordId: aliceWriteData.message.recordId
                });
                const aliceDeleteWriteReply = yield dwn.processMessage(alice.did, aliceDeleteWriteData.message);
                expect(aliceDeleteWriteReply.status.code).to.equal(202);
                const aliceQueryWriteAfterAliceDeleteData = yield TestDataGenerator.generateRecordsQuery({
                    author: alice,
                    filter: { recordId: aliceWriteData.message.recordId }
                });
                const aliceQueryWriteAfterAliceDeleteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceDeleteData.message);
                expect(aliceQueryWriteAfterAliceDeleteReply.status.code).to.equal(200);
                expect((_e = aliceQueryWriteAfterAliceDeleteReply.entries) === null || _e === void 0 ? void 0 : _e.length).to.equal(0);
                // alice writes a new record with the same data
                const aliceRewriteData = yield TestDataGenerator.generateRecordsWrite({
                    author: alice,
                    data
                });
                const aliceRewriteReply = yield dwn.processMessage(alice.did, aliceRewriteData.message, aliceRewriteData.dataStream);
                expect(aliceRewriteReply.status.code).to.equal(202);
                const aliceQueryWriteAfterAliceRewriteData = yield TestDataGenerator.generateRecordsQuery({
                    author: alice,
                    filter: { recordId: aliceRewriteData.message.recordId }
                });
                const aliceQueryWriteAfterAliceRewriteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceRewriteData.message);
                expect(aliceQueryWriteAfterAliceRewriteReply.status.code).to.equal(200);
                expect((_f = aliceQueryWriteAfterAliceRewriteReply.entries) === null || _f === void 0 ? void 0 : _f.length).to.equal(1);
                expect(aliceQueryWriteAfterAliceRewriteReply.entries[0].encodedData).to.equal(encodedData);
            }));
            describe('event log', () => {
                it('should include RecordsDelete event and keep initial RecordsWrite event', () => __awaiter(this, void 0, void 0, function* () {
                    const alice = yield DidKeyResolver.generate();
                    const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
                    const writeReply = yield dwn.processMessage(alice.did, message, dataStream);
                    expect(writeReply.status.code).to.equal(202);
                    const recordsDelete = yield RecordsDelete.create({
                        recordId: message.recordId,
                        authorizationSignatureInput: Jws.createSignatureInput(alice)
                    });
                    const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
                    expect(deleteReply.status.code).to.equal(202);
                    const events = yield eventLog.getEvents(alice.did);
                    expect(events.length).to.equal(2);
                    const writeMessageCid = yield Message.getCid(message);
                    const deleteMessageCid = yield Message.getCid(recordsDelete.message);
                    const expectedMessageCids = new Set([writeMessageCid, deleteMessageCid]);
                    for (const { messageCid } of events) {
                        expectedMessageCids.delete(messageCid);
                    }
                    expect(expectedMessageCids.size).to.equal(0);
                }));
                it('should only keep first write and delete when subsequent writes happen', () => __awaiter(this, void 0, void 0, function* () {
                    const { message, author, dataStream, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
                    TestStubGenerator.stubDidResolver(didResolver, [author]);
                    const reply = yield dwn.processMessage(author.did, message, dataStream);
                    expect(reply.status.code).to.equal(202);
                    const newWrite = yield RecordsWrite.createFrom({
                        unsignedRecordsWriteMessage: recordsWrite.message,
                        published: true,
                        authorizationSignatureInput: Jws.createSignatureInput(author)
                    });
                    const newWriteReply = yield dwn.processMessage(author.did, newWrite.message);
                    expect(newWriteReply.status.code).to.equal(202);
                    const recordsDelete = yield RecordsDelete.create({
                        recordId: message.recordId,
                        authorizationSignatureInput: Jws.createSignatureInput(author)
                    });
                    const deleteReply = yield dwn.processMessage(author.did, recordsDelete.message);
                    expect(deleteReply.status.code).to.equal(202);
                    const events = yield eventLog.getEvents(author.did);
                    expect(events.length).to.equal(2);
                    const deletedMessageCid = yield Message.getCid(newWrite.message);
                    for (const { messageCid } of events) {
                        if (messageCid === deletedMessageCid) {
                            expect.fail(`${messageCid} should not exist`);
                        }
                    }
                }));
            });
        });
        it('should return 401 if signature check fails', () => __awaiter(this, void 0, void 0, function* () {
            const { author, message } = yield TestDataGenerator.generateRecordsDelete();
            const tenant = author.did;
            // setting up a stub did resolver & message store
            // intentionally not supplying the public key so a different public key is generated to simulate invalid signature
            const mismatchingPersona = yield TestDataGenerator.generatePersona({ did: author.did, keyId: author.keyId });
            const didResolver = TestStubGenerator.createDidResolverStub(mismatchingPersona);
            const messageStore = stubInterface();
            const dataStore = stubInterface();
            const recordsDeleteHandler = new RecordsDeleteHandler(didResolver, messageStore, dataStore, eventLog);
            const reply = yield recordsDeleteHandler.handle({ tenant, message });
            expect(reply.status.code).to.equal(401);
        }));
        it('should return 400 if fail parsing the message', () => __awaiter(this, void 0, void 0, function* () {
            const { author, message } = yield TestDataGenerator.generateRecordsDelete();
            const tenant = author.did;
            // setting up a stub method resolver & message store
            const messageStore = stubInterface();
            const dataStore = stubInterface();
            const recordsDeleteHandler = new RecordsDeleteHandler(didResolver, messageStore, dataStore, eventLog);
            // stub the `parse()` function to throw an error
            sinon.stub(RecordsDelete, 'parse').throws('anyError');
            const reply = yield recordsDeleteHandler.handle({ tenant, message });
            expect(reply.status.code).to.equal(400);
        }));
    });
}
//# sourceMappingURL=records-delete.spec.js.map