Skip to content

Commit

Permalink
Merge pull request inversify#112 from inversify/refactor/add-pre-dest…
Browse files Browse the repository at this point in the history
…roy-decorator

Add pre destroy decorator
  • Loading branch information
notaphplover authored Nov 18, 2024
2 parents c38ecc8 + 4d00b36 commit c2bb3f9
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { beforeAll, describe, expect, it } from '@jest/globals';

import { InversifyCoreError } from '../../error/models/InversifyCoreError';
import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind';
import { MaybeClassMetadata } from '../models/MaybeClassMetadata';
import { updateMaybeClassMetadataPreDestroy } from './updateMaybeClassMetadataPreDestroy';

describe(updateMaybeClassMetadataPreDestroy.name, () => {
describe('having metadata with no postConstructorMethodName', () => {
let metadataFixture: MaybeClassMetadata;
let methodNameFixture: string | symbol;

beforeAll(() => {
metadataFixture = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: undefined,
},
properties: new Map(),
};
methodNameFixture = Symbol();
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
result =
updateMaybeClassMetadataPreDestroy(methodNameFixture)(
metadataFixture,
);
});

it('should return MaybeClassMetadata', () => {
const expected: MaybeClassMetadata = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: methodNameFixture,
},
properties: new Map(),
};

expect(result).toStrictEqual(expected);
});
});
});

describe('having metadata with postConstructorMethodName', () => {
let metadataFixture: MaybeClassMetadata;
let methodNameFixture: string | symbol;

beforeAll(() => {
metadataFixture = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: 'postConstructorMethodName',
},
properties: new Map(),
};
methodNameFixture = Symbol();
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
try {
updateMaybeClassMetadataPreDestroy(methodNameFixture)(
metadataFixture,
);
} catch (error: unknown) {
result = error;
}
});

it('should throw InversifyCoreError', () => {
const expectedErrorProperties: Partial<InversifyCoreError> = {
kind: InversifyCoreErrorKind.injectionDecoratorConflict,
message: 'Unexpected duplicated preDestroy decorator',
};

expect(result).toBeInstanceOf(InversifyCoreError);
expect(result).toStrictEqual(
expect.objectContaining(expectedErrorProperties),
);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { InversifyCoreError } from '../../error/models/InversifyCoreError';
import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind';
import { MaybeClassMetadata } from '../models/MaybeClassMetadata';

export function updateMaybeClassMetadataPreDestroy(
methodName: string | symbol,
): (metadata: MaybeClassMetadata) => MaybeClassMetadata {
return (metadata: MaybeClassMetadata): MaybeClassMetadata => {
if (metadata.lifecycle.preDestroyMethodName !== undefined) {
throw new InversifyCoreError(
InversifyCoreErrorKind.injectionDecoratorConflict,
'Unexpected duplicated preDestroy decorator',
);
}

metadata.lifecycle.preDestroyMethodName = methodName;

return metadata;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { beforeAll, describe, expect, it } from '@jest/globals';

import 'reflect-metadata';

import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils';

import { classMetadataReflectKey } from '../../reflectMetadata/data/classMetadataReflectKey';
import { ClassMetadata } from '../models/ClassMetadata';
import { preDestroy } from './preDestroy';

describe(preDestroy.name, () => {
describe('when called', () => {
let result: unknown;

beforeAll(() => {
class Foo {
@preDestroy()
public initialize(): void {}
}

result = getReflectMetadata(Foo, classMetadataReflectKey);
});

it('should return expected metadata', () => {
const expected: ClassMetadata = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: 'initialize',
},
properties: new Map(),
};

expect(result).toStrictEqual(expected);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

jest.mock('@inversifyjs/reflect-metadata-utils');

import { updateReflectMetadata } from '@inversifyjs/reflect-metadata-utils';

jest.mock('../actions/updateMaybeClassMetadataPreDestroy');
jest.mock('../calculations/getDefaultClassMetadata');
jest.mock('../calculations/handleInjectionError');

import { classMetadataReflectKey } from '../../reflectMetadata/data/classMetadataReflectKey';
import { updateMaybeClassMetadataPreDestroy } from '../actions/updateMaybeClassMetadataPreDestroy';
import { getDefaultClassMetadata } from '../calculations/getDefaultClassMetadata';
import { handleInjectionError } from '../calculations/handleInjectionError';
import { ClassMetadata } from '../models/ClassMetadata';
import { MaybeClassMetadata } from '../models/MaybeClassMetadata';
import { preDestroy } from './preDestroy';

describe(preDestroy.name, () => {
let targetFixture: object;
let propertyKeyFixture: string | symbol;
let descriptorFixture: TypedPropertyDescriptor<unknown>;

beforeAll(() => {
targetFixture = class Foo {}.prototype;
propertyKeyFixture = Symbol();
descriptorFixture = {};
});

describe('when caled', () => {
let classMetadataFixture: ClassMetadata;

let updateMaybeClassMetadataPostConstructorResult: jest.Mock<
(metadata: MaybeClassMetadata) => MaybeClassMetadata
>;

let result: unknown;

beforeAll(() => {
classMetadataFixture = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: undefined,
},
properties: new Map(),
};

updateMaybeClassMetadataPostConstructorResult = jest.fn();

(
getDefaultClassMetadata as jest.Mock<typeof getDefaultClassMetadata>
).mockReturnValueOnce(classMetadataFixture);

(
updateMaybeClassMetadataPreDestroy as jest.Mock<
typeof updateMaybeClassMetadataPreDestroy
>
).mockReturnValueOnce(updateMaybeClassMetadataPostConstructorResult);

result = preDestroy()(
targetFixture,
propertyKeyFixture,
descriptorFixture,
);
});

afterAll(() => {
jest.clearAllMocks();
});

it('should call getDefaultClassMetadata()', () => {
expect(getDefaultClassMetadata).toHaveBeenCalledTimes(1);
expect(getDefaultClassMetadata).toHaveBeenCalledWith();
});

it('should call updateMaybeClassMetadataPreDestroy()', () => {
expect(updateMaybeClassMetadataPreDestroy).toHaveBeenCalledTimes(1);
expect(updateMaybeClassMetadataPreDestroy).toHaveBeenCalledWith(
propertyKeyFixture,
);
});

it('should call updateReflectMetadata()', () => {
expect(updateReflectMetadata).toHaveBeenCalledTimes(1);
expect(updateReflectMetadata).toHaveBeenCalledWith(
targetFixture.constructor,
classMetadataReflectKey,
classMetadataFixture,
updateMaybeClassMetadataPostConstructorResult,
);
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
});

describe('when caled, and updateReflectMetadata throws an Error', () => {
let classMetadataFixture: ClassMetadata;
let errorFixture: Error;

let updateMaybeClassMetadataPostConstructorResult: jest.Mock<
(metadata: MaybeClassMetadata) => MaybeClassMetadata
>;

let result: unknown;

beforeAll(() => {
classMetadataFixture = {
constructorArguments: [],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: undefined,
},
properties: new Map(),
};

errorFixture = new Error('error-fixture');

updateMaybeClassMetadataPostConstructorResult = jest.fn();

(
getDefaultClassMetadata as jest.Mock<typeof getDefaultClassMetadata>
).mockReturnValueOnce(classMetadataFixture);

(
updateMaybeClassMetadataPreDestroy as jest.Mock<
typeof updateMaybeClassMetadataPreDestroy
>
).mockReturnValueOnce(updateMaybeClassMetadataPostConstructorResult);

(
updateReflectMetadata as jest.Mock<typeof updateReflectMetadata>
).mockImplementation((): never => {
throw errorFixture;
});

result = preDestroy()(
targetFixture,
propertyKeyFixture,
descriptorFixture,
);
});

afterAll(() => {
jest.clearAllMocks();
});

it('should call getDefaultClassMetadata()', () => {
expect(getDefaultClassMetadata).toHaveBeenCalledTimes(1);
expect(getDefaultClassMetadata).toHaveBeenCalledWith();
});

it('should call updateMaybeClassMetadataPreDestroy()', () => {
expect(updateMaybeClassMetadataPreDestroy).toHaveBeenCalledTimes(1);
expect(updateMaybeClassMetadataPreDestroy).toHaveBeenCalledWith(
propertyKeyFixture,
);
});

it('should call updateReflectMetadata()', () => {
expect(updateReflectMetadata).toHaveBeenCalledTimes(1);
expect(updateReflectMetadata).toHaveBeenCalledWith(
targetFixture.constructor,
classMetadataReflectKey,
classMetadataFixture,
updateMaybeClassMetadataPostConstructorResult,
);
});

it('should call handleInjectionError', () => {
expect(handleInjectionError).toHaveBeenCalledTimes(1);
expect(handleInjectionError).toHaveBeenCalledWith(
targetFixture,
propertyKeyFixture,
undefined,
errorFixture,
);
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { updateReflectMetadata } from '@inversifyjs/reflect-metadata-utils';

import { classMetadataReflectKey } from '../../reflectMetadata/data/classMetadataReflectKey';
import { updateMaybeClassMetadataPreDestroy } from '../actions/updateMaybeClassMetadataPreDestroy';
import { getDefaultClassMetadata } from '../calculations/getDefaultClassMetadata';
import { handleInjectionError } from '../calculations/handleInjectionError';

export function preDestroy(): MethodDecorator {
return <T>(
target: object,
propertyKey: string | symbol,
_descriptor: TypedPropertyDescriptor<T>,
): void => {
try {
updateReflectMetadata(
target.constructor,
classMetadataReflectKey,
getDefaultClassMetadata(),
updateMaybeClassMetadataPreDestroy(propertyKey),
);
} catch (error: unknown) {
handleInjectionError(target, propertyKey, undefined, error);
}
};
}

0 comments on commit c2bb3f9

Please sign in to comment.