diff --git a/dist/js/application.d.ts b/dist/js/application.d.ts index fd78625..884211d 100644 --- a/dist/js/application.d.ts +++ b/dist/js/application.d.ts @@ -4,5 +4,6 @@ import { type ApplicationMixin, type ApplicationStaticMixin } from "./applicatio type Base = typeof NamedDefaultableInMemoryEntity & Constructor & ApplicationStaticMixin; declare const Application_base: Base; export default class Application extends Application_base { + calculateHash(): string; } export {}; diff --git a/dist/js/application.js b/dist/js/application.js index 129810e..6570046 100644 --- a/dist/js/application.js +++ b/dist/js/application.js @@ -1,8 +1,12 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const entity_1 = require("@mat3ra/code/dist/js/entity"); +const utils_1 = require("@mat3ra/utils"); const applicationMixin_1 = require("./applicationMixin"); class Application extends entity_1.NamedDefaultableInMemoryEntity { + calculateHash() { + return utils_1.Utils.hash.calculateHashFromObject(utils_1.Utils.specific.removeTimestampableKeysFromConfig(this.toJSON())); + } } exports.default = Application; (0, applicationMixin_1.applicationMixin)(Application.prototype); diff --git a/pyproject.toml b/pyproject.toml index a5c6692..a38a4a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,8 @@ tests = [ "pytest", "pytest-cov", "mat3ra-esse", - "mat3ra-utils" + "mat3ra-utils>=2026.2.3", + "mat3ra-standata" ] all = [ "mat3ra-ade[tests]", @@ -94,4 +95,3 @@ pythonpath = [ testpaths = [ "tests/py" ] - diff --git a/src/js/application.ts b/src/js/application.ts index 7a980d0..f18d5ae 100644 --- a/src/js/application.ts +++ b/src/js/application.ts @@ -1,5 +1,6 @@ import { NamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; +import { Utils } from "@mat3ra/utils"; import { type ApplicationMixin, @@ -12,7 +13,13 @@ type Base = typeof NamedDefaultableInMemoryEntity & Constructor & ApplicationStaticMixin; -export default class Application extends (NamedDefaultableInMemoryEntity as Base) {} +export default class Application extends (NamedDefaultableInMemoryEntity as Base) { + calculateHash() { + return Utils.hash.calculateHashFromObject( + Utils.specific.removeTimestampableKeysFromConfig(this.toJSON()), + ); + } +} applicationMixin(Application.prototype); applicationStaticMixin(Application); diff --git a/src/py/mat3ra/ade/application.py b/src/py/mat3ra/ade/application.py index ed2c8c2..fbce130 100644 --- a/src/py/mat3ra/ade/application.py +++ b/src/py/mat3ra/ade/application.py @@ -1,5 +1,6 @@ from mat3ra.code.entity import InMemoryEntitySnakeCase from mat3ra.esse.models.software.application import ApplicationSchemaBase +from mat3ra.utils.object import calculate_hash_from_object, remove_timestampable_keys class Application(ApplicationSchemaBase, InMemoryEntitySnakeCase): @@ -26,3 +27,5 @@ def is_using_material(self) -> bool: def get_short_name(self) -> str: return self.short_name if self.short_name else self.name + def calculate_hash(self) -> str: + return calculate_hash_from_object(remove_timestampable_keys(self.to_dict())) diff --git a/tests/fixtures/application_hash.json b/tests/fixtures/application_hash.json new file mode 100644 index 0000000..b9273da --- /dev/null +++ b/tests/fixtures/application_hash.json @@ -0,0 +1,8 @@ +{ + "standata": { + "name": "espresso", + "version": "6.3", + "build": "GNU" + }, + "hash": "e4f4762afefb659c36b6ac2d08d860cc" +} diff --git a/tests/js/application.test.ts b/tests/js/application.test.ts index e473fe9..a9b3f71 100644 --- a/tests/js/application.test.ts +++ b/tests/js/application.test.ts @@ -1,5 +1,8 @@ /* eslint-disable no-unused-expressions */ +import { ApplicationStandata } from "@mat3ra/standata"; import { expect } from "chai"; +import { readFileSync } from "fs"; +import { resolve } from "path"; import Application from "../../src/js/application"; import type { CreateApplicationConfig } from "../../src/js/ApplicationRegistry"; @@ -134,4 +137,16 @@ describe("Application", () => { expect(schema).to.have.property("$id"); }); }); + + it("calculateHash matches fixture", () => { + const fixture = JSON.parse( + readFileSync(resolve(__dirname, "../fixtures/application_hash.json"), "utf-8"), + ); + const { name, version, build } = fixture.standata; + const standata = new ApplicationStandata(); + const configs = standata.getByApplicationName(name) as any[]; + const config = configs.find((a) => a.version === version && a.build === build); + const app = new Application(config); + expect(app.calculateHash()).to.equal(fixture.hash); + }); }); diff --git a/tests/py/test_application.py b/tests/py/test_application.py index 00ab98c..f6b5d14 100644 --- a/tests/py/test_application.py +++ b/tests/py/test_application.py @@ -1,4 +1,8 @@ +import json +from pathlib import Path + from mat3ra.ade import Application +from mat3ra.standata.applications import ApplicationStandata from mat3ra.utils import assertion APPLICATION_DEFAULT_FIELDS = { @@ -86,3 +90,17 @@ def test_application_from_dict(): app = Application(**config) expected = {**config} assertion.assert_deep_almost_equal(expected, app.model_dump(exclude_unset=True)) + + +def test_calculate_hash_matches_fixture(): + fixture_path = Path(__file__).parent.parent / "fixtures" / "application_hash.json" + fixture = json.loads(fixture_path.read_text()) + + st = fixture["standata"] + [config] = [ + a + for a in ApplicationStandata.get_by_name(st["name"]) + if a.get("version") == st["version"] and a.get("build") == st["build"] + ] + app = Application(**config) + assert app.calculate_hash() == fixture["hash"]