-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(repository): add tests for hasManyThrough helpers
- Loading branch information
Agnes Lin
committed
May 7, 2020
1 parent
8c35877
commit dbf9cbd
Showing
1 changed file
with
275 additions
and
0 deletions.
There are no files selected for viewing
275 changes: 275 additions & 0 deletions
275
...c/__tests__/unit/repositories/relations-helpers/resolve-has-many-through-metadata.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
// Copyright IBM Corp. 2019. All Rights Reserved. | ||
// Node module: @loopback/repository | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {expect} from '@loopback/testlab'; | ||
import { | ||
Entity, | ||
HasManyDefinition, | ||
model, | ||
ModelDefinition, | ||
property, | ||
RelationType, | ||
} from '../../../..'; | ||
import { | ||
createTargetConstraint, | ||
createThroughConstraint, | ||
HasManyThroughResolvedDefinition, | ||
resolveHasManyThroughMetadata, | ||
} from '../../../../relations/has-many/has-many-through.helper'; | ||
|
||
describe('HasManyThroughHelpers', () => { | ||
context('createThroughConstraint', () => { | ||
it('can create constraint with both foreign keys', () => { | ||
const p1 = createProduct({id: 9}); | ||
const resolved = { | ||
name: 'products', | ||
type: 'hasMany', | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
through: { | ||
model: () => CategoryProductLink, | ||
keyFrom: 'categoryId', | ||
keyTo: 'productId', | ||
}, | ||
} as HasManyThroughResolvedDefinition; | ||
const result = createThroughConstraint(resolved, 1, p1); | ||
expect(result).to.containEql({categoryId: 1, productId: 9}); | ||
}); | ||
}); | ||
context('createTargetConstraint', () => { | ||
it('', () => { | ||
const through1 = createCategoryProductLink({ | ||
id: 1, | ||
categoryId: 2, | ||
productId: 9, | ||
}); | ||
const through2 = createCategoryProductLink({ | ||
id: 2, | ||
categoryId: 2, | ||
productId: 8, | ||
}); | ||
const resolved = { | ||
name: 'products', | ||
type: 'hasMany', | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
through: { | ||
model: () => CategoryProductLink, | ||
keyFrom: 'categoryId', | ||
keyTo: 'productId', | ||
}, | ||
} as HasManyThroughResolvedDefinition; | ||
|
||
// single through model | ||
let result = createTargetConstraint(resolved, [through1]); | ||
expect(result).to.containEql({id: 9}); | ||
// multiple through models | ||
result = createTargetConstraint(resolved, [through1, through2]); | ||
expect(result).to.containEql({id: {inq: [9, 8]}}); | ||
}); | ||
}); | ||
context('resolveHasManyThroughMetadata', () => { | ||
it('throws if the wrong metadata type is used', async () => { | ||
const metadata: unknown = { | ||
name: 'category', | ||
type: RelationType.hasOne, | ||
targetsMany: false, | ||
source: Category, | ||
target: () => Category, | ||
}; | ||
|
||
expect(() => { | ||
resolveHasManyThroughMetadata(metadata as HasManyDefinition); | ||
}).to.throw( | ||
/Invalid hasOne definition for Category#category: relation type must be HasMany/, | ||
); | ||
}); | ||
|
||
it('throws if the through is not resolvable', async () => { | ||
const metadata: unknown = { | ||
name: 'category', | ||
type: RelationType.hasMany, | ||
targetsMany: true, | ||
source: Category, | ||
through: {model: 'random'}, | ||
target: () => Category, | ||
}; | ||
|
||
expect(() => { | ||
resolveHasManyThroughMetadata(metadata as HasManyDefinition); | ||
}).to.throw( | ||
/Invalid hasMany definition for Category#category: through.model must be a type resolver/, | ||
); | ||
}); | ||
|
||
describe('resolves through.keyTo/keyFrom', () => { | ||
it('resolves metadata with complete hasManyThrough definition', () => { | ||
const metadata = { | ||
name: 'products', | ||
type: RelationType.hasMany, | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
|
||
through: { | ||
model: () => CategoryProductLink, | ||
keyFrom: 'categoryId', | ||
keyTo: 'productId', | ||
}, | ||
}; | ||
const meta = resolveHasManyThroughMetadata( | ||
metadata as HasManyDefinition, | ||
); | ||
|
||
expect(meta).to.eql(resolvedMetadata); | ||
}); | ||
|
||
it('infers through.keyFrom if it is not provided', () => { | ||
const metadata = { | ||
name: 'products', | ||
type: RelationType.hasMany, | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
|
||
through: { | ||
model: () => CategoryProductLink, | ||
// no through.keyFrom | ||
keyTo: 'productId', | ||
}, | ||
}; | ||
const meta = resolveHasManyThroughMetadata( | ||
metadata as HasManyDefinition, | ||
); | ||
|
||
expect(meta).to.eql(resolvedMetadata); | ||
}); | ||
|
||
it('infers through.keyTo if it is not provided', () => { | ||
const metadata = { | ||
name: 'products', | ||
type: RelationType.hasMany, | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
|
||
through: { | ||
model: () => CategoryProductLink, | ||
keyFrom: 'categoryId', | ||
// no through.keyTo | ||
}, | ||
}; | ||
|
||
const meta = resolveHasManyThroughMetadata( | ||
metadata as HasManyDefinition, | ||
); | ||
|
||
expect(meta).to.eql(resolvedMetadata); | ||
}); | ||
|
||
it('throws if through.keyFrom, through.keyTo, and default foreign key name are not provided in through', async () => { | ||
const metadata = { | ||
name: 'categories', | ||
type: RelationType.hasMany, | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
|
||
through: { | ||
model: () => InvalidThrough, | ||
keyTo: 'productId', | ||
}, | ||
}; | ||
|
||
expect(() => { | ||
resolveHasManyThroughMetadata(metadata as HasManyDefinition); | ||
}).to.throw( | ||
/Invalid hasMany definition for Category#categories: through model InvalidThrough is missing definition of source foreign key/, | ||
); | ||
}); | ||
}); | ||
}); | ||
/****** HELPERS *******/ | ||
|
||
@model() | ||
class Category extends Entity { | ||
@property({id: true}) | ||
id: number; | ||
|
||
constructor(data: Partial<Category>) { | ||
super(data); | ||
} | ||
} | ||
|
||
@model() | ||
class Product extends Entity { | ||
@property({id: true}) | ||
id: number; | ||
|
||
constructor(data: Partial<Product>) { | ||
super(data); | ||
} | ||
} | ||
|
||
@model() | ||
class CategoryProductLink extends Entity { | ||
@property({id: true}) | ||
id: number; | ||
@property() | ||
categoryId: number; | ||
@property() | ||
productId: number; | ||
|
||
constructor(data: Partial<Product>) { | ||
super(data); | ||
} | ||
} | ||
|
||
const resolvedMetadata = { | ||
name: 'products', | ||
type: 'hasMany', | ||
targetsMany: true, | ||
source: Category, | ||
keyFrom: 'id', | ||
target: () => Product, | ||
keyTo: 'id', | ||
through: { | ||
model: () => CategoryProductLink, | ||
keyFrom: 'categoryId', | ||
keyTo: 'productId', | ||
}, | ||
}; | ||
|
||
class InvalidThrough extends Entity {} | ||
InvalidThrough.definition = new ModelDefinition('InvalidThrough') | ||
.addProperty('id', { | ||
type: 'number', | ||
id: true, | ||
required: true, | ||
}) | ||
// lack through.keyFrom | ||
.addProperty('productId', {type: 'number'}); | ||
|
||
function createCategoryProductLink(properties: Partial<CategoryProductLink>) { | ||
return new CategoryProductLink(properties); | ||
} | ||
function createProduct(properties: Partial<Product>) { | ||
return new Product(properties); | ||
} | ||
}); |