Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F3893683
cached-value.subtest.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
cached-value.subtest.ts
View Options
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import
assert
from
"assert"
;
import
{
withStoppedApp
,
withRunningApp
,
TestAppConstructor
,
}
from
"../../../test_utils/with-test-app"
;
import
{
assertThrowsAsync
}
from
"../../../test_utils/assert-throws-async"
;
import
{
getDateTime
}
from
"../../../utils/get-datetime"
;
import
{
App
,
Context
,
Collection
,
FieldTypes
,
Field
,
SuperContext
,
CollectionItem
,
}
from
"../../../main"
;
import
Bluebird
from
"bluebird"
;
import
type
{
ItemListResult
}
from
"../../../chip-types/item-list"
;
import
type
MockRestApi
from
"../../../test_utils/rest-api"
;
import
type
{
CollectionResponse
,
ItemCreatedResponse
,
ItemResponse
,
}
from
"../../../test_utils/rest-api"
;
import
{
TestApp
}
from
"../../../test_utils/test-app"
;
import
{
ClockEventDescription
,
CollectionRefreshCondition
,
RefreshCondition
,
}
from
"../../event-description"
;
import
CachedValue
from
"./cached-value"
;
import
Int
from
"./int"
;
import
{
sleep
}
from
"../../../test_utils/sleep"
;
const
action_to_status
:
{
[
name
:
string
]
:
string
}
=
{
create
:
"created"
,
open
:
"open"
,
suspend
:
"suspended"
,
close
:
"closed"
,
};
const
MIN_VALUE
=
0
;
const
extend
=
(
is_status_field_desired
:
boolean
,
clear_database_on_stop
=
true
)
=>
(
t
:
TestAppConstructor
)
=>
{
const
account_fields
:
{
[
field_name
:
string
]
:
Field
}
=
{
username
:
new
FieldTypes
.
Username
(),
number
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
Int
({
min
:
MIN_VALUE
,
}),
{
get_value
:
async
()
=>
{
return
0
;
},
refresh_on
:
[],
initial_value
:
0
,
}
),
date_time
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
DateTime
(),
{
get_value
:
async
()
=>
new
Date
(
"2018-01-01"
).
getTime
(),
refresh_on
:
make_refresh_on
(),
initial_value
:
0
,
}),
};
if
(
is_status_field_desired
)
{
account_fields
.
status
=
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
Enum
(
Object
.
values
(
action_to_status
)),
{
get_value
:
async
(
context
:
Context
,
item
:
CollectionItem
)
=>
{
context
.
app
.
Logger
.
debug3
(
"STATUS FIELD"
,
`calculating value for
${
item
.
id
}
`
);
const
{
items
}
=
await
context
.
app
.
collections
.
actions
.
list
(
new
context
.
app
.
SuperContext
())
.
filter
({
account
:
item
.
id
})
.
sort
({
"_metadata.modified_at"
:
"desc"
})
.
paginate
({
items
:
1
})
.
fetch
();
context
.
app
.
Logger
.
debug3
(
"STATUS FIELD"
,
"New cached value is"
,
action_to_status
[
items
[
0
].
get
(
"name"
)
as
keyof
typeof
action_to_status
]
);
return
action_to_status
[
items
[
0
].
get
(
"name"
)
as
keyof
typeof
action_to_status
];
},
refresh_on
:
make_refresh_on
(),
initial_value
:
"created"
,
}
);
}
const
accounts
=
new
(
class
extends
Collection
{
name
=
"accounts"
;
fields
=
{
...
account_fields
,
};
})();
const
actions
=
new
(
class
extends
Collection
{
name
=
"actions"
;
fields
=
{
name
:
new
FieldTypes
.
Enum
([
"create"
,
"open"
,
"suspend"
,
"close"
]),
account
:
new
FieldTypes
.
SingleReference
(
"accounts"
),
};
})();
return
class
extends
t
{
collections
=
{
...
App
.
BaseCollections
,
actions
,
accounts
,
};
clear_database_on_stop
=
clear_database_on_stop
;
};
};
describe
(
"cached-value"
,
()
=>
{
async
function
add_account
(
rest_api
:
MockRestApi
,
account
:
{
username
:
string
;
number
?:
number
;
date_time
?:
string
}
)
{
const
{
id
}
=
(
await
rest_api
.
post
(
"/api/v1/collections/accounts"
,
account
))
as
ItemCreatedResponse
;
await
rest_api
.
post
(
"/api/v1/collections/actions"
,
{
name
:
"create"
,
account
:
id
,
});
return
id
;
}
async
function
add_a_few_accounts
(
app
:
App
)
{
return
Bluebird
.
map
([
1
,
2
,
3
,
4
,
5
],
async
(
i
)
=>
app
.
collections
.
accounts
.
suCreate
({
username
:
`user_
${
i
}
`
,
number
:
i
,
})
);
}
it
(
"Correctly fills in cached-values if such field is added later"
,
async
()
=>
{
let
account_ids
:
string
[]
=
[];
await
withStoppedApp
(
extend
(
false
,
false
),
async
({
app
,
rest_api
})
=>
{
await
app
.
start
();
account_ids
=
await
Bluebird
.
map
([
"user_1"
,
"user_2"
],
(
username
)
=>
add_account
(
rest_api
,
{
username
})
);
await
rest_api
.
post
(
"/api/v1/collections/actions"
,
{
name
:
"suspend"
,
account
:
account_ids
[
1
],
});
},
"cached-fill"
);
await
withRunningApp
(
extend
(
true
),
async
({
rest_api
})
=>
{
await
assert_status_equals
(
rest_api
,
account_ids
[
0
],
"created"
);
await
assert_status_equals
(
rest_api
,
account_ids
[
1
],
"suspended"
);
},
"cached-fill"
);
});
it
(
"Correctly updates cached-value on create"
,
async
()
=>
withRunningApp
(
extend
(
true
),
async
({
rest_api
})
=>
{
const
account_ids
=
await
Bluebird
.
map
([
"user_1"
,
"user_2"
],
(
username
)
=>
add_account
(
rest_api
,
{
username
})
);
await
assert_status_equals
(
rest_api
,
account_ids
[
0
],
"created"
);
const
actions
=
[
"open"
,
"suspend"
,
"close"
];
await
Bluebird
.
each
(
actions
,
async
(
action
)
=>
{
await
rest_api
.
post
(
"/api/v1/collections/actions"
,
{
name
:
action
,
account
:
account_ids
[
0
],
});
const
status
=
action_to_status
[
action
];
await
assert_status_equals
(
rest_api
,
account_ids
[
0
],
status
);
await
assert_status_equals
(
rest_api
,
account_ids
[
1
],
"created"
);
});
}));
it
(
"Correctly updates cached-value on update"
,
async
()
=>
withRunningApp
(
extend
(
true
),
async
({
rest_api
})
=>
{
const
account_id
=
await
add_account
(
rest_api
,
{
username
:
"user_1"
,
});
const
{
items
:
[{
id
:
action_id
}],
}
=
(
await
rest_api
.
get
(
"/api/v1/collections/actions"
,
{
data
:
{
account
:
account_id
},
// TODO: check if passing the query here works under the new MockRestAPI
}))
as
CollectionResponse
;
await
rest_api
.
patch
(
`/api/v1/collections/actions/
${
action_id
}
`
,
{
name
:
"open"
,
});
await
assert_status_equals
(
rest_api
,
account_id
,
"open"
);
}));
it
(
"Respects is_proper_value of base field type"
,
async
()
=>
withRunningApp
(
extend
(
true
),
async
({
app
})
=>
{
const
value
=
-
1
;
await
assertThrowsAsync
(
async
()
=>
app
.
collections
.
accounts
.
suCreate
({
username
:
"user_2"
,
number
:
value
,
}),
(
error
)
=>
{
assert
.
strictEqual
(
//@eslint-ignore
error
.
data
.
field_messages
.
number
.
message
,
app
.
i18n
(
"too_small_integer"
,
[
value
,
MIN_VALUE
])
);
}
);
}));
it
(
"Respects filters of base field type"
,
async
()
=>
withRunningApp
(
extend
(
true
),
async
({
app
,
rest_api
})
=>
{
await
add_a_few_accounts
(
app
);
const
{
items
:
accounts
}
=
(
await
rest_api
.
get
(
"/api/v1/collections/accounts?filter[number][>]=3"
))
as
CollectionResponse
;
assert
.
strictEqual
(
accounts
.
length
,
2
);
}));
it
(
"Respects format of base field type"
,
async
()
=>
withRunningApp
(
extend
(
true
),
async
({
rest_api
})
=>
{
const
id
=
await
add_account
(
rest_api
,
{
username
:
"user_1"
});
const
expected_datetime
=
getDateTime
(
new
Date
(
"2018-01-01"
),
"yyyy-mm-dd hh:mm:ss"
);
const
actual_datetime
=
(
(
await
rest_api
.
get
(
`/api/v1/collections/accounts/
${
id
}
?format[date_time]=human_readable`
))
as
CollectionResponse
).
items
[
0
].
date_time
as
string
;
assert
.
strictEqual
(
actual_datetime
,
expected_datetime
);
}));
it
(
"Properly responds to recursive edits"
,
async
()
=>
withStoppedApp
(
extend
(
true
),
async
({
app
,
app_class
,
base_url
,
uniq_id
,
env
,
port
})
=>
{
await
assertThrowsAsync
(
async
()
=>
{
const
HappyNumbers
=
class
HappyNumbers
extends
Collection
{
fields
=
{
number
:
new
FieldTypes
.
Int
(),
double_number
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
Int
(),
{
get_value
:
async
(
context
:
Context
,
number
:
CollectionItem
)
=>
{
const
response
=
// @ts-ignore
await
app
.
collections
[
"happy-numbers"
]
.
list
(
context
)
.
ids
([
number
.
id
])
.
fetch
();
return
(
response
.
items
[
0
].
get
(
"number"
)
as
number
)
*
2
;
},
refresh_on
:
[
new
CollectionRefreshCondition
(
"happy-numbers"
,
"after:create"
,
async
([,
item
])
=>
[
item
.
id
]
),
],
initial_value
:
0
,
}),
};
};
const
new_app
=
new
(
class
extends
app_class
{
collections
=
{
...
super
.
collections
,
"happy-numbers"
:
new
HappyNumbers
(),
};
})(
uniq_id
,
env
,
port
,
base_url
);
await
new_app
.
start
();
},
(
e
)
=>
{
assert
.
strictEqual
(
e
.
message
,
`In the happy-numbers collection definition you've tried to create the double_number cached-value field that refers to the collection itself. Consider using 'derived-value' field type to avoid problems with endless recurrence.`
);
}
);
}));
it
(
"should pass friendship scenario"
,
async
()
=>
{
return
withRunningApp
(
(
test_app_type
)
=>
{
return
class
extends
test_app_type
{
collections
=
{
...
TestApp
.
BaseCollections
,
people
:
new
(
class
extends
Collection
{
fields
=
{
name
:
new
FieldTypes
.
Text
(),
popularity
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
Int
({
min
:
0
}),
{
refresh_on
:
[
new
CollectionRefreshCondition
(
"who-likes-who"
,
"after:create"
,
async
([,
item
])
=>
[
item
.
get
(
"likes_this_person"
)
as
string
,
]
),
],
get_value
:
async
function
(
context
,
item
)
{
const
is_liked_by
=
(
await
context
.
app
.
collections
[
"who-likes-who"
]
.
suList
()
.
filter
({
likes_this_person
:
item
.
id
,
})
.
fetch
())
as
ItemListResult
<
any
>
;
return
is_liked_by
.
items
.
length
;
},
initial_value
:
0
,
}
),
};
})(),
"who-likes-who"
:
new
(
class
extends
Collection
{
fields
=
{
this_person
:
new
FieldTypes
.
SingleReference
(
"people"
),
likes_this_person
:
new
FieldTypes
.
SingleReference
(
"people"
),
};
})(),
};
};
},
async
({
rest_api
})
=>
{
const
alice
=
await
rest_api
.
post
(
"/api/v1/collections/people"
,
{
name
:
"alice"
,
});
const
bob
=
await
rest_api
.
post
(
"/api/v1/collections/people"
,
{
name
:
"bob"
,
});
const
friendship
=
await
rest_api
.
post
(
"/api/v1/collections/who-likes-who"
,
{
this_person
:
bob
.
id
as
string
,
likes_this_person
:
alice
.
id
as
string
,
});
const
response
=
await
rest_api
.
get
(
`/api/v1/collections/people/
${
alice
.
id
as
string
}
`
);
assert
.
strictEqual
(
response
.
items
[
0
].
popularity
,
1
);
await
rest_api
.
delete
(
`/api/v1/collections/who-likes-who/
${
friendship
.
id
as
string
}
`
);
}
);
});
it
(
"behaves correctly when the initial value is null"
,
()
=>
withRunningApp
(
(
test_app
)
=>
class
extends
test_app
{
collections
=
{
...
App
.
BaseCollections
,
jobs
:
new
(
class
extends
Collection
{
fields
=
{
title
:
new
FieldTypes
.
Text
(),
};
})(),
hasdefault
:
new
(
class
extends
Collection
{
fields
=
{
isdefault
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
SingleReference
(
"jobs"
),
{
refresh_on
:
[
new
CollectionRefreshCondition
(
"jobs"
,
"after:edit"
,
async
([,
item
])
=>
[
item
.
id
]
),
],
get_value
:
async
function
(
context
,
item
)
{
const
job
=
await
context
.
app
.
collections
[
"jobs"
].
getByID
(
context
,
item
.
id
);
return
job
.
id
;
},
initial_value
:
null
,
}
),
};
})(),
};
},
async
({
app
})
=>
{
const
hasdefault
=
await
app
.
collections
.
hasdefault
.
suCreate
({});
assert
.
strictEqual
(
hasdefault
.
get
(
"isdefault"
),
null
);
}
));
it
(
"handles field types that depend on being .inited"
,
()
=>
withRunningApp
(
(
test_app
)
=>
class
extends
test_app
{
collections
=
{
...
App
.
BaseCollections
,
jobs
:
new
(
class
extends
Collection
{
fields
=
{
title
:
new
FieldTypes
.
Text
(),
hasjob
:
new
FieldTypes
.
SingleReference
(
"hasjob"
),
};
})(),
hasjob
:
new
(
class
extends
Collection
{
fields
=
{
job
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
SingleReference
(
"jobs"
),
{
refresh_on
:
[
new
CollectionRefreshCondition
(
"jobs"
,
"after:create"
,
async
([,
item
])
=>
[
item
.
get
(
"hasjob"
)]
),
],
get_value
:
async
function
(
context
,
item
)
{
const
{
items
:
[
job
],
}
=
await
context
.
app
.
collections
.
jobs
.
suList
()
.
filter
({
hasjob
:
item
.
id
})
.
fetch
();
return
job
.
id
;
},
initial_value
:
null
,
}
),
};
})(),
};
},
async
({
app
})
=>
{
const
hasjob
=
await
app
.
collections
.
hasjob
.
suCreate
({});
const
job
=
await
app
.
collections
.
jobs
.
suCreate
({
title
:
"any"
,
hasjob
:
hasjob
.
id
,
});
const
{
items
:
[
hasjob_after
],
}
=
await
app
.
collections
.
hasjob
.
suList
().
fetch
();
assert
.
strictEqual
(
hasjob_after
.
get
(
"job"
),
job
.
id
);
}
));
it
(
"handles clock-based tasks"
,
()
=>
withStoppedApp
(
(
app_class
)
=>
{
const
count
=
(
function
*
()
{
yield
1
;
while
(
true
)
{
yield
2
;
}
})();
return
class
extends
app_class
{
collections
=
{
...
App
.
BaseCollections
,
tick_tock
:
new
(
class
TickTock
extends
Collection
{
fields
=
{
seconds
:
new
CachedValue
(
new
Int
(),
{
initial_value
:
0
,
refresh_on
:
[
new
ClockEventDescription
(
"* * * * * *"
,
async
([
context
])
=>
{
const
{
items
}
=
await
context
.
app
.
collections
.
tick_tock
.
list
(
context
)
.
fetch
();
return
items
.
map
((
i
)
=>
i
.
id
);
},
(
app
:
App
)
=>
new
SuperContext
(
app
,
Date
.
now
())
),
],
get_value
:
async
(
item
)
=>
{
return
count
.
next
().
value
;
},
}),
};
})(),
};
};
},
async
(
test
)
=>
{
await
test
.
app
.
start
();
await
test
.
app
.
collections
.
tick_tock
.
suCreate
({});
await
sleep
(
3000
);
const
{
items
:
[
item
],
}
=
await
test
.
app
.
collections
.
tick_tock
.
suList
().
fetch
();
assert
.
strictEqual
(
item
.
get
(
"seconds"
),
2
);
await
test
.
app
.
stop
();
}
));
describe
(
"variant with derived_value"
,
()
=>
{
it
(
"works on a simple scenario"
,
()
=>
withRunningApp
(
(
test_app
)
=>
class
extends
test_app
{
collections
=
{
...
App
.
BaseCollections
,
comments
:
new
(
class
extends
Collection
{
fields
=
{
text
:
new
FieldTypes
.
Text
(),
product
:
new
FieldTypes
.
SingleReference
(
"products"
),
};
})(),
products
:
new
(
class
extends
Collection
{
fields
=
{
name
:
new
FieldTypes
.
Text
(),
price
:
new
FieldTypes
.
Float
(),
// a product is considered hot if it's cheap or has at least one comment
is_hot
:
new
FieldTypes
.
CachedValue
(
new
FieldTypes
.
Boolean
(),
{
refresh_on
:
[
new
CollectionRefreshCondition
(
"comments"
,
[
"after:create"
,
"after:edit"
,
"after:remove"
],
async
([,
item
])
=>
[
item
.
get
(
"product"
)
as
string
]
),
],
get_value
:
async
(
context
,
item
)
=>
{
const
price
=
await
item
.
getDecoded
(
"price"
,
context
);
const
comments_count
=
(
await
context
.
app
.
collections
[
"comments"
]
.
suList
()
.
filter
({
product
:
item
.
id
,
})
.
fetch
()
).
items
.
length
;
return
(
price
as
number
)
<
100
||
comments_count
>
0
;
},
initial_value
:
false
,
derive_from
:
[
"price"
],
}),
};
})(),
};
},
async
({
app
})
=>
{
const
context
=
new
Context
(
app
);
let
product
=
await
app
.
collections
.
products
.
create
(
context
,
{
name
:
"hehe shampoo"
,
price
:
150
,
is_hot
:
null
,
});
assert
.
strictEqual
(
product
.
get
(
"is_hot"
),
false
,
"Should be false, because the product is neither cheap nor commented on"
);
product
.
set
(
"price"
,
90
);
await
product
.
save
(
context
);
product
=
await
app
.
collections
.
products
.
getByID
(
context
,
product
.
id
);
// refreshing;
assert
.
strictEqual
(
product
.
get
(
"is_hot"
),
true
,
"Should be true, because the product is cheap"
);
product
.
set
(
"price"
,
110
);
await
product
.
save
(
context
);
product
=
await
app
.
collections
.
products
.
getByID
(
context
,
product
.
id
);
// refreshing;
assert
.
strictEqual
(
product
.
get
(
"is_hot"
),
false
,
"Should be false, because the product is expensive again"
);
await
app
.
collections
.
comments
.
create
(
context
,
{
text
:
"good stuff"
,
product
:
product
.
id
,
});
product
=
await
app
.
collections
.
products
.
getByID
(
context
,
product
.
id
);
// refreshing;
assert
.
strictEqual
(
product
.
get
(
"is_hot"
),
true
,
"Should be true, because the product has comments"
);
}
));
});
});
function
make_refresh_on
()
:
RefreshCondition
[]
{
return
[
new
CollectionRefreshCondition
(
"actions"
,
[
"after:create"
,
"after:edit"
],
async
([,
action
])
=>
[
action
.
get
(
"account"
)
as
string
]
),
];
}
async
function
assert_status_equals
(
rest_api
:
MockRestApi
,
account_id
:
string
,
expected_status
:
string
)
{
const
{
items
}
=
(
await
rest_api
.
get
(
`/api/v1/collections/accounts/
${
account_id
}
`
))
as
ItemResponse
;
assert
.
strictEqual
(
items
[
0
].
status
,
expected_status
);
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Mon, May 19, 00:51 (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
635542
Default Alt Text
cached-value.subtest.ts (17 KB)
Attached To
Mode
rS Sealious
Attached
Detach File
Event Timeline
Log In to Comment