Merge branch 'master' into enforce-example-com-emails-in-tests
This commit is contained in:
commit
2946188ab1
|
@ -6,7 +6,7 @@ import env from "../environment"
|
|||
import * as accounts from "../accounts"
|
||||
import { UserDB } from "../users"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { User } from "@budibase/types"
|
||||
import { User, UserMetadata } from "@budibase/types"
|
||||
|
||||
const EXPIRY_SECONDS = 3600
|
||||
|
||||
|
@ -15,7 +15,7 @@ const EXPIRY_SECONDS = 3600
|
|||
*/
|
||||
async function populateFromDB(userId: string, tenantId: string) {
|
||||
const db = tenancy.getTenantDB(tenantId)
|
||||
const user = await db.get<any>(userId)
|
||||
const user = await db.get<UserMetadata>(userId)
|
||||
user.budibaseAccess = true
|
||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||
const account = await accounts.getAccount(user.email)
|
||||
|
|
|
@ -1,66 +1,57 @@
|
|||
import PouchDB from "pouchdb"
|
||||
import { getPouchDB, closePouchDB } from "./couch"
|
||||
import { DocumentType } from "../constants"
|
||||
|
||||
class Replication {
|
||||
source: any
|
||||
target: any
|
||||
replication: any
|
||||
source: PouchDB.Database
|
||||
target: PouchDB.Database
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source - the DB you want to replicate or rollback to
|
||||
* @param target - the DB you want to replicate to, or rollback from
|
||||
*/
|
||||
constructor({ source, target }: any) {
|
||||
constructor({ source, target }: { source: string; target: string }) {
|
||||
this.source = getPouchDB(source)
|
||||
this.target = getPouchDB(target)
|
||||
}
|
||||
|
||||
close() {
|
||||
return Promise.all([closePouchDB(this.source), closePouchDB(this.target)])
|
||||
async close() {
|
||||
await Promise.all([closePouchDB(this.source), closePouchDB(this.target)])
|
||||
}
|
||||
|
||||
promisify(operation: any, opts = {}) {
|
||||
return new Promise(resolve => {
|
||||
operation(this.target, opts)
|
||||
.on("denied", function (err: any) {
|
||||
replicate(opts: PouchDB.Replication.ReplicateOptions = {}) {
|
||||
return new Promise<PouchDB.Replication.ReplicationResult<{}>>(resolve => {
|
||||
this.source.replicate
|
||||
.to(this.target, opts)
|
||||
.on("denied", function (err) {
|
||||
// a document failed to replicate (e.g. due to permissions)
|
||||
throw new Error(`Denied: Document failed to replicate ${err}`)
|
||||
})
|
||||
.on("complete", function (info: any) {
|
||||
.on("complete", function (info) {
|
||||
return resolve(info)
|
||||
})
|
||||
.on("error", function (err: any) {
|
||||
.on("error", function (err) {
|
||||
throw new Error(`Replication Error: ${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Two way replication operation, intended to be promise based.
|
||||
* @param opts - PouchDB replication options
|
||||
*/
|
||||
sync(opts = {}) {
|
||||
this.replication = this.promisify(this.source.sync, opts)
|
||||
return this.replication
|
||||
}
|
||||
appReplicateOpts(
|
||||
opts: PouchDB.Replication.ReplicateOptions = {}
|
||||
): PouchDB.Replication.ReplicateOptions {
|
||||
if (typeof opts.filter === "string") {
|
||||
return opts
|
||||
}
|
||||
|
||||
/**
|
||||
* One way replication operation, intended to be promise based.
|
||||
* @param opts - PouchDB replication options
|
||||
*/
|
||||
replicate(opts = {}) {
|
||||
this.replication = this.promisify(this.source.replicate.to, opts)
|
||||
return this.replication
|
||||
}
|
||||
const filter = opts.filter
|
||||
delete opts.filter
|
||||
|
||||
appReplicateOpts() {
|
||||
return {
|
||||
filter: (doc: any) => {
|
||||
...opts,
|
||||
filter: (doc: any, params: any) => {
|
||||
if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) {
|
||||
return false
|
||||
}
|
||||
return doc._id !== DocumentType.APP_METADATA
|
||||
if (doc._id === DocumentType.APP_METADATA) {
|
||||
return false
|
||||
}
|
||||
return filter ? filter(doc, params) : true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -75,10 +66,6 @@ class Replication {
|
|||
// take the opportunity to remove deleted tombstones
|
||||
await this.replicate()
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.replication.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
export default Replication
|
||||
|
|
|
@ -101,10 +101,7 @@ export function getBuiltinRole(roleId: string): Role | undefined {
|
|||
/**
|
||||
* Works through the inheritance ranks to see how far up the builtin stack this ID is.
|
||||
*/
|
||||
export function builtinRoleToNumber(id?: string) {
|
||||
if (!id) {
|
||||
return 0
|
||||
}
|
||||
export function builtinRoleToNumber(id: string) {
|
||||
const builtins = getBuiltinRoles()
|
||||
const MAX = Object.values(builtins).length + 1
|
||||
if (id === BUILTIN_IDS.ADMIN || id === BUILTIN_IDS.BUILDER) {
|
||||
|
|
|
@ -139,10 +139,22 @@
|
|||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
<div class="input-wrapper">
|
||||
<Input bind:value={searchTerm} thin placeholder="Search Icon" />
|
||||
<div class="input-wrapper" style={`width: ${value ? "425" : "510"}px`}>
|
||||
<Input
|
||||
bind:value={searchTerm}
|
||||
on:keyup={event => {
|
||||
if (event.key === "Enter") {
|
||||
searchForIcon()
|
||||
}
|
||||
}}
|
||||
thin
|
||||
placeholder="Search Icon"
|
||||
/>
|
||||
</div>
|
||||
<Button secondary on:click={searchForIcon}>Search</Button>
|
||||
{#if value}
|
||||
<Button primary on:click={() => (value = null)}>Clear</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="page-area">
|
||||
<div class="pager">
|
||||
|
@ -239,6 +251,7 @@
|
|||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
gap: 10px;
|
||||
}
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
|
|
|
@ -525,6 +525,38 @@
|
|||
"barTitle": "Disable button",
|
||||
"key": "disabled"
|
||||
},
|
||||
{
|
||||
"type": "icon",
|
||||
"label": "Icon",
|
||||
"key": "icon"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Gap",
|
||||
"key": "gap",
|
||||
"showInBar": true,
|
||||
"barStyle": "picker",
|
||||
"dependsOn": "icon",
|
||||
"options": [
|
||||
{
|
||||
"label": "None",
|
||||
"value": "N"
|
||||
},
|
||||
{
|
||||
"label": "Small",
|
||||
"value": "S"
|
||||
},
|
||||
{
|
||||
"label": "Medium",
|
||||
"value": "M"
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": "L"
|
||||
}
|
||||
],
|
||||
"defaultValue": "M"
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"label": "On click",
|
||||
|
|
|
@ -13,9 +13,10 @@
|
|||
export let size = "M"
|
||||
export let type = "cta"
|
||||
export let quiet = false
|
||||
export let icon = null
|
||||
export let gap = "M"
|
||||
|
||||
// For internal use only for now - not defined in the manifest
|
||||
export let icon = null
|
||||
export let active = false
|
||||
|
||||
const handleOnClick = async () => {
|
||||
|
@ -47,7 +48,7 @@
|
|||
|
||||
{#key $component.editing}
|
||||
<button
|
||||
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`}
|
||||
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type} gap-${gap}`}
|
||||
class:spectrum-Button--quiet={quiet}
|
||||
disabled={disabled || handlingOnClick}
|
||||
use:styleable={$component.styles}
|
||||
|
@ -58,15 +59,7 @@
|
|||
class:active
|
||||
>
|
||||
{#if icon}
|
||||
<svg
|
||||
class:hasText={componentText?.length > 0}
|
||||
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label={icon}
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||
</svg>
|
||||
<i class="{icon} {size}" />
|
||||
{/if}
|
||||
{componentText}
|
||||
</button>
|
||||
|
@ -92,4 +85,13 @@
|
|||
.active {
|
||||
color: var(--spectrum-global-color-blue-600);
|
||||
}
|
||||
.gap-S {
|
||||
gap: 8px;
|
||||
}
|
||||
.gap-M {
|
||||
gap: 16px;
|
||||
}
|
||||
.gap-L {
|
||||
gap: 32px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
wrap: true,
|
||||
}}
|
||||
>
|
||||
{#each buttons as { text, type, quiet, disabled, onClick, size }}
|
||||
{#each buttons as { text, type, quiet, disabled, onClick, size, icon, gap }}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
|
@ -29,6 +29,8 @@
|
|||
type,
|
||||
quiet,
|
||||
disabled,
|
||||
icon,
|
||||
gap,
|
||||
size: size || "M",
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -92,9 +92,9 @@
|
|||
{#if schemaLoaded}
|
||||
<Button
|
||||
onClick={openEditor}
|
||||
icon="Properties"
|
||||
icon="ri-filter-3-line"
|
||||
text="Filter"
|
||||
{size}
|
||||
size="XL"
|
||||
type="secondary"
|
||||
quiet
|
||||
active={filters?.length > 0}
|
||||
|
|
|
@ -106,6 +106,21 @@ export async function save(ctx: UserCtx<SaveRoleRequest, SaveRoleResponse>) {
|
|||
)
|
||||
role._rev = result.rev
|
||||
ctx.body = role
|
||||
|
||||
const devDb = context.getDevAppDB()
|
||||
const prodDb = context.getProdAppDB()
|
||||
|
||||
if (await prodDb.exists()) {
|
||||
const replication = new dbCore.Replication({
|
||||
source: devDb.name,
|
||||
target: prodDb.name,
|
||||
})
|
||||
await replication.replicate({
|
||||
filter: (doc: any, params: any) => {
|
||||
return doc._id && doc._id.startsWith("role_")
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function destroy(ctx: UserCtx<void, DestroyRoleResponse>) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { generateUserFlagID, InternalTables } from "../../db/utils"
|
||||
import { getFullUser } from "../../utilities/users"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import { cache, context } from "@budibase/backend-core"
|
||||
import {
|
||||
ContextUserMetadata,
|
||||
Ctx,
|
||||
|
|
|
@ -16,8 +16,9 @@ import * as setup from "./utilities"
|
|||
import { AppStatus } from "../../../db/utils"
|
||||
import { events, utils, context } from "@budibase/backend-core"
|
||||
import env from "../../../environment"
|
||||
import type { App } from "@budibase/types"
|
||||
import { type App } from "@budibase/types"
|
||||
import tk from "timekeeper"
|
||||
import * as uuid from "uuid"
|
||||
|
||||
describe("/applications", () => {
|
||||
let config = setup.getConfig()
|
||||
|
@ -251,7 +252,7 @@ describe("/applications", () => {
|
|||
|
||||
describe("permissions", () => {
|
||||
it("should only return apps a user has access to", async () => {
|
||||
const user = await config.createUser({
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
@ -260,6 +261,81 @@ describe("/applications", () => {
|
|||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
builder: {
|
||||
apps: [config.getProdAppId()],
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should only return apps a user has access to through a custom role", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const role = await config.api.roles.save({
|
||||
name: "Test",
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
roles: {
|
||||
[config.getProdAppId()]: role.name,
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it.only("should only return apps a user has access to through a custom role on a group", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const roleName = uuid.v4().replace(/-/g, "")
|
||||
const role = await config.api.roles.save({
|
||||
name: roleName,
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
const group = await config.createGroup(role._id!)
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
userGroups: [group._id!],
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -299,11 +299,11 @@ export default class TestConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
withUser(user: User, f: () => Promise<void>) {
|
||||
async withUser(user: User, f: () => Promise<void>) {
|
||||
const oldUser = this.user
|
||||
this.user = user
|
||||
try {
|
||||
return f()
|
||||
return await f()
|
||||
} finally {
|
||||
this.user = oldUser
|
||||
}
|
||||
|
@ -363,6 +363,7 @@ export default class TestConfiguration {
|
|||
_id,
|
||||
...existing,
|
||||
...config,
|
||||
_rev: existing._rev,
|
||||
email,
|
||||
roles,
|
||||
tenantId,
|
||||
|
@ -372,11 +373,12 @@ export default class TestConfiguration {
|
|||
admin,
|
||||
}
|
||||
await sessions.createASession(_id, {
|
||||
sessionId: "sessionid",
|
||||
sessionId: this.sessionIdForUser(_id),
|
||||
tenantId: this.getTenantId(),
|
||||
csrfToken: this.csrfToken,
|
||||
})
|
||||
const resp = await db.put(user)
|
||||
await cache.user.invalidateUser(_id)
|
||||
return {
|
||||
_rev: resp.rev,
|
||||
...user,
|
||||
|
@ -384,9 +386,7 @@ export default class TestConfiguration {
|
|||
}
|
||||
|
||||
async createUser(user: Partial<User> = {}): Promise<User> {
|
||||
const resp = await this.globalUser(user)
|
||||
await cache.user.invalidateUser(resp._id!)
|
||||
return resp
|
||||
return await this.globalUser(user)
|
||||
}
|
||||
|
||||
async createGroup(roleId: string = roles.BUILTIN_ROLE_IDS.BASIC) {
|
||||
|
@ -416,6 +416,10 @@ export default class TestConfiguration {
|
|||
})
|
||||
}
|
||||
|
||||
sessionIdForUser(userId: string): string {
|
||||
return `sessionid-${userId}`
|
||||
}
|
||||
|
||||
async login({
|
||||
roleId,
|
||||
userId,
|
||||
|
@ -442,13 +446,13 @@ export default class TestConfiguration {
|
|||
})
|
||||
}
|
||||
await sessions.createASession(userId, {
|
||||
sessionId: "sessionid",
|
||||
sessionId: this.sessionIdForUser(userId),
|
||||
tenantId: this.getTenantId(),
|
||||
})
|
||||
// have to fake this
|
||||
const authObj = {
|
||||
userId,
|
||||
sessionId: "sessionid",
|
||||
sessionId: this.sessionIdForUser(userId),
|
||||
tenantId: this.getTenantId(),
|
||||
}
|
||||
const authToken = jwt.sign(authObj, coreEnv.JWT_SECRET as Secret)
|
||||
|
@ -470,7 +474,7 @@ export default class TestConfiguration {
|
|||
const user = this.getUser()
|
||||
const authObj: AuthToken = {
|
||||
userId: user._id!,
|
||||
sessionId: "sessionid",
|
||||
sessionId: this.sessionIdForUser(user._id!),
|
||||
tenantId,
|
||||
}
|
||||
const authToken = jwt.sign(authObj, coreEnv.JWT_SECRET as Secret)
|
||||
|
|
|
@ -11,6 +11,7 @@ import { BackupAPI } from "./backup"
|
|||
import { AttachmentAPI } from "./attachment"
|
||||
import { UserAPI } from "./user"
|
||||
import { QueryAPI } from "./query"
|
||||
import { RoleAPI } from "./role"
|
||||
|
||||
export default class API {
|
||||
table: TableAPI
|
||||
|
@ -25,6 +26,7 @@ export default class API {
|
|||
attachment: AttachmentAPI
|
||||
user: UserAPI
|
||||
query: QueryAPI
|
||||
roles: RoleAPI
|
||||
|
||||
constructor(config: TestConfiguration) {
|
||||
this.table = new TableAPI(config)
|
||||
|
@ -39,5 +41,6 @@ export default class API {
|
|||
this.attachment = new AttachmentAPI(config)
|
||||
this.user = new UserAPI(config)
|
||||
this.query = new QueryAPI(config)
|
||||
this.roles = new RoleAPI(config)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
AccessibleRolesResponse,
|
||||
FetchRolesResponse,
|
||||
FindRoleResponse,
|
||||
SaveRoleRequest,
|
||||
SaveRoleResponse,
|
||||
} from "@budibase/types"
|
||||
import { Expectations, TestAPI } from "./base"
|
||||
|
||||
export class RoleAPI extends TestAPI {
|
||||
fetch = async (expectations?: Expectations) => {
|
||||
return await this._get<FetchRolesResponse>(`/api/roles`, {
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
find = async (roleId: string, expectations?: Expectations) => {
|
||||
return await this._get<FindRoleResponse>(`/api/roles/${roleId}`, {
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
save = async (body: SaveRoleRequest, expectations?: Expectations) => {
|
||||
return await this._post<SaveRoleResponse>(`/api/roles`, {
|
||||
body,
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
destroy = async (roleId: string, expectations?: Expectations) => {
|
||||
return await this._delete(`/api/roles/${roleId}`, {
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
accesssible = async (expectations?: Expectations) => {
|
||||
return await this._get<AccessibleRolesResponse>(`/api/roles/accessible`, {
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -390,24 +390,41 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
}
|
||||
)
|
||||
|
||||
// Match a document against all criteria
|
||||
const docMatch = (doc: any) => {
|
||||
return (
|
||||
stringMatch(doc) &&
|
||||
fuzzyMatch(doc) &&
|
||||
rangeMatch(doc) &&
|
||||
equalMatch(doc) &&
|
||||
notEqualMatch(doc) &&
|
||||
emptyMatch(doc) &&
|
||||
notEmptyMatch(doc) &&
|
||||
oneOf(doc) &&
|
||||
contains(doc) &&
|
||||
containsAny(doc) &&
|
||||
notContains(doc)
|
||||
)
|
||||
}
|
||||
const filterFunctions: Record<SearchQueryOperators, (doc: any) => boolean> =
|
||||
{
|
||||
string: stringMatch,
|
||||
fuzzy: fuzzyMatch,
|
||||
range: rangeMatch,
|
||||
equal: equalMatch,
|
||||
notEqual: notEqualMatch,
|
||||
empty: emptyMatch,
|
||||
notEmpty: notEmptyMatch,
|
||||
oneOf: oneOf,
|
||||
contains: contains,
|
||||
containsAny: containsAny,
|
||||
notContains: notContains,
|
||||
}
|
||||
|
||||
// Process all docs
|
||||
const activeFilterKeys: SearchQueryOperators[] = Object.entries(query || {})
|
||||
.filter(
|
||||
([key, value]: [string, any]) =>
|
||||
!["allOr", "onEmptyFilter"].includes(key) &&
|
||||
value &&
|
||||
Object.keys(value as Record<string, any>).length > 0
|
||||
)
|
||||
.map(([key]) => key as any)
|
||||
|
||||
const results: boolean[] = activeFilterKeys.map(filterKey => {
|
||||
return filterFunctions[filterKey]?.(doc) ?? false
|
||||
})
|
||||
|
||||
if (query!.allOr) {
|
||||
return results.some(result => result === true)
|
||||
} else {
|
||||
return results.every(result => result === true)
|
||||
}
|
||||
}
|
||||
return docs.filter(docMatch)
|
||||
}
|
||||
|
||||
|
|
|
@ -47,10 +47,7 @@ describe("runLuceneQuery", () => {
|
|||
},
|
||||
]
|
||||
|
||||
function buildQuery(
|
||||
filterKey: string,
|
||||
value: { [key: string]: any }
|
||||
): SearchQuery {
|
||||
function buildQuery(filters: { [filterKey: string]: any }): SearchQuery {
|
||||
const query: SearchQuery = {
|
||||
string: {},
|
||||
fuzzy: {},
|
||||
|
@ -63,8 +60,13 @@ describe("runLuceneQuery", () => {
|
|||
notContains: {},
|
||||
oneOf: {},
|
||||
containsAny: {},
|
||||
allOr: false,
|
||||
}
|
||||
query[filterKey as SearchQueryOperators] = value
|
||||
|
||||
for (const filterKey in filters) {
|
||||
query[filterKey as SearchQueryOperators] = filters[filterKey]
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
|
@ -73,16 +75,17 @@ describe("runLuceneQuery", () => {
|
|||
})
|
||||
|
||||
it("should return matching rows for equal filter", () => {
|
||||
const query = buildQuery("equal", {
|
||||
order_status: 4,
|
||||
const query = buildQuery({
|
||||
equal: { order_status: 4 },
|
||||
})
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it("should return matching row for notEqual filter", () => {
|
||||
const query = buildQuery("notEqual", {
|
||||
order_status: 4,
|
||||
const query = buildQuery({
|
||||
notEqual: { order_status: 4 },
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3])
|
||||
})
|
||||
|
||||
|
@ -90,48 +93,56 @@ describe("runLuceneQuery", () => {
|
|||
expect(
|
||||
runLuceneQuery(
|
||||
docs,
|
||||
buildQuery("fuzzy", {
|
||||
description: "sm",
|
||||
buildQuery({
|
||||
fuzzy: { description: "sm" },
|
||||
})
|
||||
).map(row => row.description)
|
||||
).toEqual(["Small box"])
|
||||
expect(
|
||||
runLuceneQuery(
|
||||
docs,
|
||||
buildQuery("string", {
|
||||
description: "SM",
|
||||
buildQuery({
|
||||
string: { description: "SM" },
|
||||
})
|
||||
).map(row => row.description)
|
||||
).toEqual(["Small box"])
|
||||
})
|
||||
|
||||
it("should return rows within a range filter", () => {
|
||||
const query = buildQuery("range", {
|
||||
customer_id: {
|
||||
low: 500,
|
||||
high: 1000,
|
||||
const query = buildQuery({
|
||||
range: {
|
||||
customer_id: {
|
||||
low: 500,
|
||||
high: 1000,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3])
|
||||
})
|
||||
|
||||
it("should return rows with numeric strings within a range filter", () => {
|
||||
const query = buildQuery("range", {
|
||||
customer_id: {
|
||||
low: "500",
|
||||
high: "1000",
|
||||
const query = buildQuery({
|
||||
range: {
|
||||
customer_id: {
|
||||
low: "500",
|
||||
high: "1000",
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3])
|
||||
})
|
||||
|
||||
it("should return rows with ISO date strings within a range filter", () => {
|
||||
const query = buildQuery("range", {
|
||||
order_date: {
|
||||
low: "2016-01-04T00:00:00.000Z",
|
||||
high: "2016-01-11T00:00:00.000Z",
|
||||
const query = buildQuery({
|
||||
range: {
|
||||
order_date: {
|
||||
low: "2016-01-04T00:00:00.000Z",
|
||||
high: "2016-01-11T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2])
|
||||
})
|
||||
|
||||
|
@ -150,40 +161,88 @@ describe("runLuceneQuery", () => {
|
|||
label: "",
|
||||
},
|
||||
]
|
||||
const query = buildQuery("range", {
|
||||
order_date: {
|
||||
low: "2016-01-04T00:00:00.000Z",
|
||||
high: "2016-01-11T00:00:00.000Z",
|
||||
|
||||
const query = buildQuery({
|
||||
range: {
|
||||
order_date: {
|
||||
low: "2016-01-04T00:00:00.000Z",
|
||||
high: "2016-01-11T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query)).toEqual(docs)
|
||||
})
|
||||
|
||||
it("should return rows with matches on empty filter", () => {
|
||||
const query = buildQuery("empty", {
|
||||
label: null,
|
||||
const query = buildQuery({
|
||||
empty: {
|
||||
label: null,
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([1])
|
||||
})
|
||||
|
||||
it("should return rows with matches on notEmpty filter", () => {
|
||||
const query = buildQuery("notEmpty", {
|
||||
label: null,
|
||||
const query = buildQuery({
|
||||
notEmpty: {
|
||||
label: null,
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2, 3])
|
||||
})
|
||||
|
||||
test.each([[523, 259], "523,259"])(
|
||||
"should return rows with matches on numeric oneOf filter",
|
||||
input => {
|
||||
let query = buildQuery("oneOf", {
|
||||
customer_id: input,
|
||||
const query = buildQuery({
|
||||
oneOf: {
|
||||
customer_id: input,
|
||||
},
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.customer_id)).toEqual([
|
||||
259, 523,
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
test.each([
|
||||
[false, []],
|
||||
[true, [1, 2, 3]],
|
||||
])("should return %s if allOr is %s ", (allOr, expectedResult) => {
|
||||
const query = buildQuery({
|
||||
allOr,
|
||||
oneOf: { staff_id: [10] },
|
||||
contains: { description: ["box"] },
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual(
|
||||
expectedResult
|
||||
)
|
||||
})
|
||||
|
||||
it("should return matching results if allOr is true and only one filter matches with different operands", () => {
|
||||
const query = buildQuery({
|
||||
allOr: true,
|
||||
equal: { order_status: 4 },
|
||||
oneOf: { label: ["FRAGILE"] },
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it("should handle when a value is null or undefined", () => {
|
||||
const query = buildQuery({
|
||||
allOr: true,
|
||||
equal: { order_status: null },
|
||||
oneOf: { label: ["FRAGILE"] },
|
||||
})
|
||||
|
||||
expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2])
|
||||
})
|
||||
})
|
||||
|
||||
describe("buildLuceneQuery", () => {
|
||||
|
|
|
@ -5,4 +5,5 @@ export interface Role extends Document {
|
|||
inherits?: string
|
||||
permissions: { [key: string]: string[] }
|
||||
version?: string
|
||||
name: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue