// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.
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 * as fs from "fs-extra";
import path from "path";
import * as crypto from "crypto";
import { isActionsAssertion } from "./assertions.js";
import { CallbackSigner, LocalSigner } from "./Signer.js";
import { Reader } from "./Reader.js";
import { Builder } from "./Builder.js";
const tempDir = path.join(__dirname, "tmp");
class TestSigner {
    constructor(privateKey) {
        this.sign = (bytes) => __awaiter(this, void 0, void 0, function* () {
            const sign = crypto.createSign("SHA256");
            sign.update(bytes);
            sign.end();
            const signature = sign.sign(this.privateKey);
            return signature;
        });
        this.privateKey = crypto.createPrivateKey({
            key: privateKey,
            format: "pem",
        });
    }
}
describe("TestSigner", () => {
    it("should sign data", () => __awaiter(void 0, void 0, void 0, function* () {
        const signer = new TestSigner(yield fs.readFile("./tests/fixtures/certs/es256.pem"));
        const bytes = Buffer.from("Hello, World!");
        const signature = yield signer.sign(bytes);
        expect(signature.length).toBeGreaterThan(0);
    }));
});
describe("Builder", () => {
    const parent_json = `{
            "title": "c2pa-bindings Test",
            "format": "image/jpeg",
            "instance_id": "12345",
            "relationship": "parentOf"
            }`;
    const thumbnail_ref = {
        format: "ingredient/jpeg",
        identifier: "5678",
    };
    const manifestDefinition = {
        vendor: "test",
        claim_generator: "test-generator",
        claim_generator_info: [
            {
                name: "c2pa_test",
                version: "1.0.0",
            },
        ],
        title: "Test_Manifest",
        format: "image/jpeg",
        instance_id: "1234",
        thumbnail: {
            format: "image/jpeg",
            identifier: "thumbnail.jpg",
        },
        ingredients: [
            {
                title: "Test",
                format: "image/jpeg",
                instance_id: "12345",
                relationship: "componentOf",
                thumbnail: { format: "image/jpeg", identifier: "ingredient-thumb.jpg" },
                resources: { resources: {} },
            },
        ],
        assertions: [
            {
                label: "org.test.assertion",
                data: {},
            },
        ],
        resources: { resources: {} },
    };
    let testAssertion;
    let testThumbnail;
    let source;
    let publicKey;
    let privateKey;
    beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
        yield fs.ensureDir(tempDir);
        // Read all test files once
        source = {
            buffer: yield fs.readFile("./tests/fixtures/CA.jpg"),
            mimeType: "image/jpeg",
        };
        testThumbnail = yield fs.readFile("./tests/fixtures/thumbnail.jpg");
        publicKey = yield fs.readFile("./tests/fixtures/certs/es256.pub");
        privateKey = yield fs.readFile("./tests/fixtures/certs/es256.pem");
        testAssertion = JSON.stringify({ answer: 42 });
    }));
    afterAll(() => __awaiter(void 0, void 0, void 0, function* () {
        yield fs.remove(tempDir);
    }));
    it("should build a manifest store", () => __awaiter(void 0, void 0, void 0, function* () {
        const test_definition = {
            claim_generator: "test-generator",
            claim_generator_info: [
                {
                    name: "c2pa-js tests",
                    version: "1.0.0",
                },
            ],
            format: "image/tiff",
            instance_id: "1234",
            title: "builder-test-manifest",
            vendor: "test",
            thumbnail: thumbnail_ref,
            label: "ABCDE",
            ingredients: [],
            assertions: [],
            resources: { resources: {} },
        };
        const builder = Builder.withJson(test_definition);
        yield builder.addIngredient(parent_json, source);
        builder.addAssertion("org.test.assertion", "assertion");
        yield builder.addResource(thumbnail_ref.identifier, {
            buffer: Buffer.from("12345"),
            mimeType: "jpeg",
        });
        // Wait for any pending async operations
        yield Promise.resolve();
        const definition = builder.getManifestDefinition();
        expect(definition.vendor).toBe("test");
        expect(definition.title).toBe("builder-test-manifest");
        expect(definition.format).toBe("image/tiff");
        expect(definition.instance_id).toBe("1234");
        // addIngredient with a source asset imports the ingredient's manifest chain
        // (populating definition.ingredients) AND additionally records a created
        // c2pa.ingredient.v3 assertion in definition.assertions.
        expect(definition.ingredients[0].title).toStrictEqual("c2pa-bindings Test");
        const ingredientAssertion = definition.assertions.find((a) => a.label === "c2pa.ingredient.v3");
        expect(ingredientAssertion).toBeDefined();
        expect(ingredientAssertion.data["dc:title"]).toBe("c2pa-bindings Test");
        expect(definition.assertions.some((a) => a.label === "org.test.assertion")).toBe(true);
        expect(definition.label).toBe("ABCDE");
    }));
    describe("Sign and Archive", () => {
        let builder;
        beforeEach(() => {
            builder = Builder.withJson(manifestDefinition);
            builder.updateManifestProperty("claim_version", 2);
        });
        beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
            // Add ingredients and resources after builder is initialized
            yield builder.addIngredient(parent_json, source);
            yield builder.addResource("thumbnail.jpg", {
                mimeType: "jpeg",
                buffer: testThumbnail,
            });
            yield builder.addResource("ingredient-thumb.jpg", {
                mimeType: "jpeg",
                buffer: testThumbnail,
            });
            yield builder.addResource("prompt.txt", {
                buffer: Buffer.from("a random prompt"),
                mimeType: "text/plain",
            });
            builder.addAssertion("org.life.meaning", testAssertion);
            builder.addAssertion("org.life.meaning.json", testAssertion, "Json");
            // Wait for any pending operations
            yield Promise.resolve();
        }));
        it("should sign data", () => __awaiter(void 0, void 0, void 0, function* () {
            const dest = { path: path.join(tempDir, "signed.jpg") };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            const bytes = builder.sign(signer, source, dest);
            expect(bytes.length).toBeGreaterThan(0);
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            const activeManifest = reader.getActive();
            expect(manifestStore.validation_status[0].code).toBe("signingCredential.untrusted");
            expect(manifestStore.active_manifest).not.toBeUndefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should add a CBOR assertion, sign, and verify it in the signed manifest", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            // Add the c2pa.watermarked action as a CBOR assertion
            const actionsAssertion = {
                actions: [
                    {
                        action: "c2pa.watermarked",
                    },
                ],
            };
            builder.addAssertion("c2pa.actions", actionsAssertion, "Cbor");
            // Sign the manifest
            const dest = { path: path.join(tempDir, "cbor_signed.jpg") };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            builder.sign(signer, source, dest);
            // Read and verify the assertion in the signed manifest
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const activeManifest = reader.getActive();
            const cborAssertion = (_a = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.assertions) === null || _a === void 0 ? void 0 : _a.find((a) => a.label === "c2pa.actions.v2");
            expect(cborAssertion).toBeDefined();
            if (isActionsAssertion(cborAssertion)) {
                const actions = cborAssertion.data.actions.map((a) => a.action);
                expect(actions).toContain("c2pa.watermarked");
            }
            else {
                throw new Error("CBOR assertion does not have the expected structure");
            }
        }));
        it("should archive and construct from archive", () => __awaiter(void 0, void 0, void 0, function* () {
            const dest = {
                buffer: null,
            };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            const outputPath = path.join(tempDir, "archive.zip");
            const archive = { path: outputPath };
            yield builder.toArchive(archive);
            const builder2 = yield Builder.fromArchive(archive);
            builder2.sign(signer, source, dest);
            const reader = yield Reader.fromAsset({
                buffer: dest.buffer,
                mimeType: "jpeg",
            });
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            const activeManifest = reader.getActive();
            expect(manifestStore.validation_status[0].code).toBe("signingCredential.untrusted");
            expect(manifestStore.active_manifest).not.toBeUndefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should populate buffer when archiving to buffer", () => __awaiter(void 0, void 0, void 0, function* () {
            const archive = {
                buffer: null,
            };
            yield builder.toArchive(archive);
            expect(archive.buffer).not.toBeNull();
            expect(archive.buffer.length).toBeGreaterThan(0);
        }));
        it("should write archive to file", () => __awaiter(void 0, void 0, void 0, function* () {
            const archivePath = path.join(tempDir, "archive_file_test.zip");
            const archive = { path: archivePath };
            yield builder.toArchive(archive);
            // Verify file was created and has content
            expect(yield fs.pathExists(archivePath)).toBe(true);
            const stats = yield fs.stat(archivePath);
            expect(stats.size).toBeGreaterThan(0);
        }));
        it("should construct reader directly from builder archive buffer", () => __awaiter(void 0, void 0, void 0, function* () {
            const settings = JSON.stringify({
                builder: {
                    generate_c2pa_archive: true,
                },
                verify: {
                    verify_after_reading: false,
                },
            });
            // Create a builder
            const simpleManifestDefinition = {
                claim_generator_info: [
                    {
                        name: "c2pa_test",
                        version: "1.0.0",
                    },
                ],
                title: "Test_Manifest",
                format: "image/jpeg",
                assertions: [],
                resources: { resources: {} },
            };
            const testBuilder = Builder.withJson(simpleManifestDefinition, settings);
            // Add an ingredient
            yield testBuilder.addIngredient(parent_json, source);
            // Create an archive from the builder written to a buffer
            const archive = {
                buffer: null,
            };
            yield testBuilder.toArchive(archive);
            // Verify buffer was populated
            expect(archive.buffer).not.toBeNull();
            expect(archive.buffer.length).toBeGreaterThan(0);
            // Construct a reader from the builder archive with mime-type "application/c2pa"
            const reader = yield Reader.fromAsset({
                buffer: archive.buffer,
                mimeType: "application/c2pa",
            }, settings);
            expect(reader).not.toBeNull();
        }));
        it("should sign data with callback to file", () => __awaiter(void 0, void 0, void 0, function* () {
            const dest = {
                path: path.join(tempDir, "callback_signed.jpg"),
            };
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const signer = new TestSigner(privateKey);
            const bytes = yield builder.signConfigAsync(signer.sign, signerConfig, source, dest);
            expect(bytes.length).toBeGreaterThan(0);
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            const activeManifest = reader.getActive();
            expect(manifestStore.validation_status[0].code).toBe("signingCredential.untrusted");
            expect(manifestStore.active_manifest).not.toBeUndefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should sign data with callback to buffer", () => __awaiter(void 0, void 0, void 0, function* () {
            const dest = {
                buffer: null,
            };
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const signer = new TestSigner(privateKey);
            const bytes = yield builder.signConfigAsync(signer.sign, signerConfig, source, dest);
            expect(bytes.length).toBeGreaterThan(0);
            const reader = yield Reader.fromAsset({
                buffer: dest.buffer,
                mimeType: "jpeg",
            });
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            const activeManifest = reader.getActive();
            expect(manifestStore.validation_status[0].code).toBe("signingCredential.untrusted");
            expect(manifestStore.active_manifest).not.toBeUndefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should sign data with callback signer to buffer", () => __awaiter(void 0, void 0, void 0, function* () {
            const dest = {
                buffer: null,
            };
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const testSigner = new TestSigner(privateKey);
            const signer = CallbackSigner.newSigner(signerConfig, testSigner.sign);
            const bytes = yield builder.signAsync(signer, source, dest);
            expect(bytes.length).toBeGreaterThan(0);
            const reader = yield Reader.fromAsset({
                buffer: dest.buffer,
                mimeType: "jpeg",
            });
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            const activeManifest = reader.getActive();
            expect(manifestStore.validation_status[0].code).toBe("signingCredential.untrusted");
            expect(manifestStore.active_manifest).not.toBeUndefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should sign a pre-computed DataHash (LocalSigner, sync)", () => __awaiter(void 0, void 0, void 0, function* () {
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            // Raw SHA-256 of the source asset bytes. With exclusions=[], the
            // resulting manifest's c2pa.hash.data.hash equals this digest.
            const hash = crypto.createHash("sha256").update(source.buffer).digest();
            const manifestBytes = builder.signDataHashedEmbeddable(signer, {
                name: "raw asset",
                alg: "sha256",
                hash,
                exclusions: [],
            }, "image/jpeg");
            expect(Buffer.isBuffer(manifestBytes)).toBe(true);
            expect(manifestBytes.length).toBeGreaterThan(0);
            // Returned bytes are preformatted for the target format (JPEG APP11
            // segment). The c2pa JUMBF box label "c2pa" appears inside.
            expect(manifestBytes.toString("binary")).toContain("c2pa");
        }));
        it("should sign a pre-computed DataHash (CallbackSigner, async)", () => __awaiter(void 0, void 0, void 0, function* () {
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const testSigner = new TestSigner(privateKey);
            const signer = CallbackSigner.newSigner(signerConfig, testSigner.sign);
            const hash = crypto.createHash("sha256").update(source.buffer).digest();
            const manifestBytes = yield builder.signDataHashedEmbeddableAsync(signer, {
                alg: "sha256",
                hash,
                exclusions: [],
            }, "image/jpeg");
            expect(Buffer.isBuffer(manifestBytes)).toBe(true);
            expect(manifestBytes.length).toBeGreaterThan(0);
            expect(manifestBytes.toString("binary")).toContain("c2pa");
        }));
        it("should preserve JSON assertion characters without escaping", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            const fingerprintAssertion = JSON.stringify({
                alg: "sha256",
                blocks: [
                    {
                        scope: {},
                        value: "test-fingerprint-data",
                    },
                ],
            });
            builder.addAssertion("org.contentauth.fingerprint", fingerprintAssertion, "Json");
            const dest = { path: path.join(tempDir, "json_test.jpg") };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            builder.sign(signer, source, dest);
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const manifest = reader.json();
            // Check that our specific JSON assertion doesn't have escaped characters
            const activeManifest = manifest.manifests[manifest.active_manifest];
            const fingerprintAssertionData = (_a = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.assertions) === null || _a === void 0 ? void 0 : _a.find((a) => a.label === "org.contentauth.fingerprint");
            expect(fingerprintAssertionData).toBeDefined();
            expect(fingerprintAssertionData === null || fingerprintAssertionData === void 0 ? void 0 : fingerprintAssertionData.data).toEqual({
                alg: "sha256",
                blocks: [
                    {
                        scope: {},
                        value: "test-fingerprint-data",
                    },
                ],
            });
            // The assertion data should not be a string with escaped quotes
            expect(typeof (fingerprintAssertionData === null || fingerprintAssertionData === void 0 ? void 0 : fingerprintAssertionData.data)).toBe("object");
            expect(JSON.stringify(fingerprintAssertionData === null || fingerprintAssertionData === void 0 ? void 0 : fingerprintAssertionData.data)).not.toContain("\\");
        }));
        it("should archive and restore builder with ingredient thumbnail", () => __awaiter(void 0, void 0, void 0, function* () {
            const manifestDefinition = {
                claim_generator_info: [
                    {
                        name: "c2pa_test",
                        version: "1.0.0",
                    },
                ],
                title: "Test_Manifest",
                format: "image/jpeg",
                ingredients: [],
                assertions: [
                    {
                        label: "c2pa.actions",
                        data: {
                            actions: [
                                {
                                    action: "c2pa.created",
                                    digitalSourceType: "http://c2pa.org/digitalsourcetype/empty",
                                },
                            ],
                        },
                    },
                ],
                resources: { resources: {} },
            };
            const builder = Builder.withJson(manifestDefinition);
            const ingredientJson = '{"title": "Test Ingredient"}';
            const testThumbnail = yield fs.readFile("./tests/fixtures/thumbnail.jpg");
            yield builder.addIngredient(ingredientJson, {
                buffer: testThumbnail,
                mimeType: "jpeg",
            });
            // Archive the builder
            const archivePath = path.join(tempDir, `ingredient_thumb_archive_${Date.now()}.zip`);
            yield builder.toArchive({ path: archivePath });
            // Ensure the file is written to disk before proceeding
            yield fs.access(archivePath);
            // Restore from archive
            const builder2 = yield Builder.fromArchive({ path: archivePath });
            // Sign with restored builder
            const dest = { buffer: null };
            const publicKey = yield fs.readFile("./tests/fixtures/certs/es256.pub");
            const privateKey = yield fs.readFile("./tests/fixtures/certs/es256.pem");
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            builder2.sign(signer, source, dest);
            const reader = yield Reader.fromAsset({
                buffer: dest.buffer,
                mimeType: "jpeg",
            });
            expect(reader).not.toBeNull();
            const manifestStore = reader.json();
            // The imported ingredient chain (thumbnail) survives the archive round-trip
            // and signing, alongside the created ingredient assertion.
            expect(JSON.stringify(manifestStore)).toContain("Test Ingredient");
            expect(JSON.stringify(manifestStore)).toContain("thumbnail.ingredient");
        }));
        it("should add ingredient with custom metadata", () => __awaiter(void 0, void 0, void 0, function* () {
            const builder = Builder.new();
            const ingredient = {
                title: "Test Ingredient",
                format: "image/jpeg",
                instance_id: "ingredient-12345",
                relationship: "componentOf",
                metadata: {
                    customString: "my custom value",
                    customNumber: 42,
                    customBool: true,
                    customObject: {
                        nested: "value",
                        count: 123,
                    },
                    customArray: ["item1", "item2", "item3"],
                },
            };
            yield builder.addIngredient(JSON.stringify(ingredient));
            // The ingredient becomes a created c2pa.ingredient.v3 assertion. Its
            // high-level fields are mapped onto the assertion field names and custom
            // metadata is passed through unchanged.
            const definition = builder.getManifestDefinition();
            const ingredientAssertion = definition.assertions.find((a) => a.label === "c2pa.ingredient.v3");
            expect(ingredientAssertion).toBeDefined();
            expect(ingredientAssertion.data).toMatchObject({
                "dc:title": ingredient.title,
                "dc:format": ingredient.format,
                instanceID: ingredient.instance_id,
                relationship: ingredient.relationship,
                metadata: ingredient.metadata,
            });
        }));
        it("should redact a thumbnail from an ingredient manifest", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const testSigner = new TestSigner(privateKey);
            const signer = CallbackSigner.newSigner(signerConfig, testSigner.sign);
            // Sign source asset with a thumbnail
            const step1Builder = Builder.withJson({
                claim_generator_info: [{ name: "c2pa_test", version: "1.0.0" }],
                title: "Asset With Thumbnail",
                format: "image/jpeg",
                instance_id: "thumb-step1-1234",
                thumbnail: { format: "image/jpeg", identifier: "thumbnail.jpg" },
            });
            yield step1Builder.addResource("thumbnail.jpg", {
                mimeType: "image/jpeg",
                buffer: testThumbnail,
            });
            const step1Dest = { buffer: null };
            yield step1Builder.signAsync(signer, source, step1Dest);
            const step1Asset = {
                buffer: step1Dest.buffer,
                mimeType: "image/jpeg",
            };
            // Verify thumbnail exists in original manifest
            const originalReader = yield Reader.fromAsset(step1Asset);
            expect(originalReader).not.toBeNull();
            const parentLabel = originalReader.activeLabel();
            expect(parentLabel).toBeDefined();
            const originalManifest = originalReader.getActive();
            expect(originalManifest === null || originalManifest === void 0 ? void 0 : originalManifest.thumbnail).toBeDefined();
            expect(originalManifest === null || originalManifest === void 0 ? void 0 : originalManifest.thumbnail).not.toBeNull();
            // Redact the thumbnail
            const thumbnailUri = `self#jumbf=/c2pa/${parentLabel}/c2pa.assertions/c2pa.thumbnail.claim`;
            const redactionBuilder = Builder.withJson({
                claim_generator_info: [{ name: "c2pa_test", version: "1.0.0" }],
                title: "Redacted Thumbnail Manifest",
                format: "image/jpeg",
                instance_id: "thumb-step2-1234",
            });
            redactionBuilder.setIntent("update");
            redactionBuilder.addRedaction(thumbnailUri, "c2pa.PII.present");
            const step2Dest = { buffer: null };
            yield redactionBuilder.signAsync(signer, step1Asset, step2Dest);
            const finalReader = yield Reader.fromAsset({
                buffer: step2Dest.buffer,
                mimeType: "image/jpeg",
            });
            expect(finalReader).not.toBeNull();
            const store = finalReader.json();
            const parentManifest = (_a = store.manifests) === null || _a === void 0 ? void 0 : _a[parentLabel];
            expect(parentManifest).toBeDefined();
            expect(parentManifest === null || parentManifest === void 0 ? void 0 : parentManifest.thumbnail).toBeUndefined();
        }));
        it("should redact an assertion from an ingredient manifest", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a, _b;
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const testSigner = new TestSigner(privateKey);
            const signer = CallbackSigner.newSigner(signerConfig, testSigner.sign);
            // Sign source asset with multiple distinct assertions
            const piiLabel = "stds.schema-org.CreativeWork";
            const retainedLabel = "org.contentauth.retained";
            const step1Builder = Builder.withJson({
                claim_generator_info: [{ name: "c2pa_test", version: "1.0.0" }],
                title: "Asset With Multiple Assertions",
                format: "image/jpeg",
                instance_id: "assert-step1-1234",
                assertions: [
                    {
                        label: piiLabel,
                        data: {
                            "@context": "http://schema.org/",
                            "@type": "CreativeWork",
                            author: [{ "@type": "Person", name: "John Doe" }],
                        },
                    },
                    {
                        label: retainedLabel,
                        data: { keep: true },
                    },
                ],
            });
            const step1Dest = { buffer: null };
            yield step1Builder.signAsync(signer, source, step1Dest);
            const step1Asset = {
                buffer: step1Dest.buffer,
                mimeType: "image/jpeg",
            };
            // Verify both assertions exist in original manifest
            const originalReader = yield Reader.fromAsset(step1Asset);
            expect(originalReader).not.toBeNull();
            const parentLabel = originalReader.activeLabel();
            expect(parentLabel).toBeDefined();
            const originalStore = originalReader.json();
            const originalLabels = originalStore.manifests[parentLabel].assertions.map((a) => a.label);
            expect(originalLabels).toContain(piiLabel);
            expect(originalLabels).toContain(retainedLabel);
            // Redact only the PII assertion
            const redactionUri = `self#jumbf=/c2pa/${parentLabel}/c2pa.assertions/${piiLabel}`;
            const redactionBuilder = Builder.withJson({
                claim_generator_info: [{ name: "c2pa_test", version: "1.0.0" }],
                title: "Redacted Assertion Manifest",
                format: "image/jpeg",
                instance_id: "assert-step2-1234",
            });
            redactionBuilder.setIntent("update");
            redactionBuilder.addRedaction(redactionUri, "c2pa.PII.present");
            const step2Dest = { buffer: null };
            yield redactionBuilder.signAsync(signer, step1Asset, step2Dest);
            const finalReader = yield Reader.fromAsset({
                buffer: step2Dest.buffer,
                mimeType: "image/jpeg",
            });
            expect(finalReader).not.toBeNull();
            const store = finalReader.json();
            const parentManifest = (_a = store.manifests) === null || _a === void 0 ? void 0 : _a[parentLabel];
            expect(parentManifest).toBeDefined();
            const assertionLabels = (_b = parentManifest === null || parentManifest === void 0 ? void 0 : parentManifest.assertions) === null || _b === void 0 ? void 0 : _b.map((a) => a.label);
            // PII assertion removed, retained assertion still present
            expect(assertionLabels).not.toContain(piiLabel);
            expect(assertionLabels).toContain(retainedLabel);
        }));
        it("should add redactions via addRedaction method", () => {
            const uri1 = "self#jumbf=/c2pa/test-label/c2pa.assertions/cawg.identity";
            const uri2 = "self#jumbf=/c2pa/test-label/c2pa.assertions/stds.schema-org.CreativeWork";
            const builder = Builder.new();
            builder.addRedaction(uri1, "c2pa.PII.present");
            builder.addRedaction(uri2, "c2pa.PII.present");
            const definition = builder.getManifestDefinition();
            expect(definition.redactions).toEqual([uri1, uri2]);
        });
        it("should test builder remote url", () => __awaiter(void 0, void 0, void 0, function* () {
            // This test mirrors the Rust test_builder_remote_url test
            // Create a simple manifest definition similar to simple_manifest_json()
            const simpleManifestDefinition = {
                claim_generator_info: [
                    {
                        name: "c2pa_test",
                        version: "1.0.0",
                    },
                ],
                title: "Test_Manifest",
                assertions: [
                    {
                        label: "c2pa.actions",
                        data: {
                            actions: [
                                {
                                    action: "c2pa.created",
                                    digitalSourceType: "http://c2pa.org/digitalsourcetype/empty",
                                },
                            ],
                        },
                    },
                ],
            };
            const builder = Builder.withJson(simpleManifestDefinition);
            // Set remote URL and no embed flag like the Rust test
            builder.setRemoteUrl("http://my_remote_url");
            builder.setNoEmbed(true);
            // Use the callback signer
            const signerConfig = {
                alg: "es256",
                certs: [publicKey],
                reserveSize: 10000,
                tsaUrl: undefined,
                directCoseHandling: false,
            };
            const testSigner = new TestSigner(privateKey);
            const signer = CallbackSigner.newSigner(signerConfig, testSigner.sign);
            // Sign the Builder and write it to the output stream
            const dest = { buffer: null };
            const manifestData = yield builder.signAsync(signer, source, dest);
            // Check to make sure we have a remote url and no manifest data embedded
            // Reading the image directly should fail since no_embed = true
            // Note: This might fail due to remote URL fetch, which is expected behavior
            try {
                yield Reader.fromAsset({
                    buffer: dest.buffer,
                    mimeType: "image/jpeg",
                });
                // If we get here, the test should fail because we expect no embedded manifest
                expect.fail("Expected Reader.fromAsset to fail when no_embed = true");
            }
            catch (error) {
                // This is expected - there should be no embedded manifest or remote fetch should fail
                expect(error).toBeDefined();
            }
            // Now validate the manifest against the written asset using the separate manifest data
            const reader = yield Reader.fromManifestDataAndAsset(manifestData, {
                buffer: dest.buffer,
                mimeType: "image/jpeg",
            });
            // Check if the manifest has the expected structure
            const activeManifest = reader.getActive();
            expect(activeManifest).toBeDefined();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.title).toBe("Test_Manifest");
        }));
        it("should add action using addAction method", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            const simpleManifestDefinition = {
                claim_generator_info: [
                    {
                        name: "c2pa_test",
                        version: "1.0.0",
                    },
                ],
                title: "Test_AddAction",
                format: "image/jpeg",
                assertions: [],
                resources: { resources: {} },
            };
            const builder = Builder.withJson(simpleManifestDefinition);
            // Add an action using addAction method
            // The action needs to be a structured object matching the c2pa Action type
            const actionJson = JSON.stringify({
                action: "c2pa.edited",
            });
            builder.addAction(actionJson);
            // Sign the manifest
            const dest = { path: path.join(tempDir, "add_action_test.jpg") };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            builder.sign(signer, source, dest);
            // Verify the action was added
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const activeManifest = reader.getActive();
            const actionsAssertion = (_a = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.assertions) === null || _a === void 0 ? void 0 : _a.find((a) => a.label === "c2pa.actions.v2");
            expect(actionsAssertion).toBeDefined();
            if (isActionsAssertion(actionsAssertion)) {
                const actions = actionsAssertion.data.actions;
                const editedAction = actions.find((a) => a.action === "c2pa.edited");
                expect(editedAction).toBeDefined();
                expect(editedAction === null || editedAction === void 0 ? void 0 : editedAction.action).toBe("c2pa.edited");
            }
            else {
                throw new Error("Actions assertion does not have the expected structure");
            }
        }));
        it("should add ingredient using addIngredient method", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a, _b;
            const simpleManifestDefinition = {
                claim_generator_info: [
                    {
                        name: "c2pa_test",
                        version: "1.0.0",
                    },
                ],
                title: "Test_AddIngredient",
                format: "image/jpeg",
                ingredients: [],
                assertions: [
                    {
                        label: "c2pa.actions",
                        data: {
                            actions: [
                                {
                                    action: "c2pa.created",
                                    digitalSourceType: "http://c2pa.org/digitalsourcetype/empty",
                                },
                            ],
                        },
                    },
                ],
                resources: { resources: {} },
            };
            const builder = Builder.withJson(simpleManifestDefinition);
            // Add an ingredient using addIngredient method
            const ingredientJson = JSON.stringify({
                title: "Test Ingredient via addIngredient",
                format: "image/jpeg",
                instance_id: "ingredient-12345",
                relationship: "componentOf",
            });
            builder.addIngredient(ingredientJson);
            // Sign the manifest
            const dest = { path: path.join(tempDir, "add_ingredient_test.jpg") };
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            builder.sign(signer, source, dest);
            // Verify the ingredient was added
            const reader = yield Reader.fromAsset(dest);
            expect(reader).not.toBeNull();
            const activeManifest = reader.getActive();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.ingredients).toBeDefined();
            expect((_a = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.ingredients) === null || _a === void 0 ? void 0 : _a.length).toBeGreaterThan(0);
            const addedIngredient = (_b = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.ingredients) === null || _b === void 0 ? void 0 : _b.find((ing) => ing.title === "Test Ingredient via addIngredient");
            expect(addedIngredient).toBeDefined();
            expect(addedIngredient === null || addedIngredient === void 0 ? void 0 : addedIngredient.instance_id).toBe("ingredient-12345");
            expect(addedIngredient === null || addedIngredient === void 0 ? void 0 : addedIngredient.relationship).toBe("componentOf");
        }));
        it("should add ingredient from reader", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            const builder1 = Builder.new();
            builder1.setIntent("edit");
            const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
            const dest1 = {
                buffer: null,
            };
            builder1.sign(signer, source, dest1);
            // Read the signed file back with Reader
            const reader = yield Reader.fromAsset({
                buffer: dest1.buffer,
                mimeType: "image/jpeg",
            });
            expect(reader).not.toBeNull();
            // Create a new builder and add ingredient from the reader
            const builder2 = Builder.new();
            const ingredient = builder2.addIngredientFromReader(reader);
            expect(ingredient).toBeDefined();
            // Verify the ingredient was added to the builder
            const definition = builder2.getManifestDefinition();
            expect(definition.ingredients).toBeDefined();
            expect(definition.ingredients.length).toBeGreaterThan(0);
            // Sign again with the new builder
            const dest2 = {
                buffer: null,
            };
            builder2.sign(signer, source, dest2);
            // Verify the ingredient is in the signed manifest
            const reader2 = yield Reader.fromAsset({
                buffer: dest2.buffer,
                mimeType: "image/jpeg",
            });
            expect(reader2).not.toBeNull();
            const activeManifest = reader2.getActive();
            expect(activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.ingredients).toBeDefined();
            expect((_a = activeManifest === null || activeManifest === void 0 ? void 0 : activeManifest.ingredients) === null || _a === void 0 ? void 0 : _a.length).toBe(1);
        }));
    });
});
