Init
This commit is contained in:
commit
fab442ea1d
55
.eslintrc.json
Normal file
55
.eslintrc.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es2017": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "stroustrup", {}],
|
||||
"camelcase": "error",
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": [1, "last"],
|
||||
"consistent-this": [1, "_this"],
|
||||
"curly": [1, "multi"],
|
||||
"eol-last": 1,
|
||||
"eqeqeq": 1,
|
||||
"func-names": 1,
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||
"max-len": [1, 180, 2], // 2 spaces per tab, max 80 chars per line
|
||||
"new-cap": 1,
|
||||
"newline-before-return": "error",
|
||||
"no-array-constructor": 1,
|
||||
"no-inner-declarations": [1, "both"],
|
||||
"no-mixed-spaces-and-tabs": 1,
|
||||
"no-multi-spaces": 2,
|
||||
"no-new-object": 1,
|
||||
"no-shadow-restricted-names": 1,
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||
"prefer-const": "error",
|
||||
"prefer-template": "error",
|
||||
"one-var": 0,
|
||||
"quote-props": ["error", "always"],
|
||||
"quotes": [1, "single"],
|
||||
"radix": 1,
|
||||
"semi": [1, "always"],
|
||||
"space-before-blocks": [1, "always"],
|
||||
"space-infix-ops": 1,
|
||||
"vars-on-top": 1,
|
||||
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||
}
|
||||
|
||||
}
|
149
.gitignore
vendored
Normal file
149
.gitignore
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
/src/bundle.js
|
||||
/src/bundle.js.map
|
||||
/live/
|
||||
!/output/
|
||||
/db/jobs.db
|
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "main",
|
||||
"program": "${workspaceFolder}/main.js"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "node",
|
||||
"name": "vscode-jest-tests",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--runInBand"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"disableOptimisticBPs": true,
|
||||
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
|
||||
}
|
||||
]
|
||||
}
|
154
Buyer.js
Normal file
154
Buyer.js
Normal file
@ -0,0 +1,154 @@
|
||||
class Buyer {
|
||||
constructor(market) {
|
||||
this.market = market;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of the sellers
|
||||
* @param product
|
||||
* @returns {*}
|
||||
*/
|
||||
buildSellerList(product) {
|
||||
const sellers = [];
|
||||
this.market.sellers.forEach((seller, i) => {
|
||||
if (seller.inventory.hasOwnProperty(product)) {
|
||||
const price = seller.quote(product);
|
||||
sellers.push({ 'id': seller.id, 'price': price, 'index': i, 'quantity': seller.inventory[product].quantity });
|
||||
}
|
||||
});
|
||||
|
||||
return sellers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an order preference table for particular product
|
||||
* @param product
|
||||
*/
|
||||
buildOrderPrefBestPrice(product) {
|
||||
const sellers = this.buildSellerList(product);
|
||||
|
||||
// sort by best price
|
||||
sellers.sort((a, b) => {
|
||||
return a.price - b.price;
|
||||
});
|
||||
|
||||
return sellers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the list based on Delivery delay
|
||||
* @param product
|
||||
* @returns {*}
|
||||
*/
|
||||
buildOrderPrefFastFill(product) {
|
||||
const sellers = this.buildSellerList(product);
|
||||
|
||||
// sort by best price
|
||||
sellers.sort((a, b) => {
|
||||
return a.deliveryWait - b.deliveryWait;
|
||||
});
|
||||
|
||||
return sellers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the list based on item quantity
|
||||
* @param product
|
||||
* @returns {*}
|
||||
*/
|
||||
buildOrderPrefMostFill(product) {
|
||||
const sellers = this.buildSellerList(product);
|
||||
|
||||
// sort by best price
|
||||
sellers.sort((a, b) => {
|
||||
return b.quantity - a.quantity;
|
||||
});
|
||||
|
||||
return sellers;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should get the best price for a given product
|
||||
* across all sellers
|
||||
*/
|
||||
getBestPrice(product) {
|
||||
let lowestPrice = null;
|
||||
this.market.sellers.forEach(seller => {
|
||||
if (seller.inventory.hasOwnProperty(product)) {
|
||||
const price = seller.quote(product);
|
||||
if (lowestPrice === null || lowestPrice > price) lowestPrice = price;
|
||||
}
|
||||
});
|
||||
|
||||
return lowestPrice || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual purchases based on preference list
|
||||
* @param preference
|
||||
* @param product
|
||||
* @param quantity
|
||||
* @returns {number}
|
||||
*/
|
||||
doPurchases(preference, product, quantity) {
|
||||
const sellerPreference = [...preference];
|
||||
let total = 0;
|
||||
let wantedQuantity = quantity;
|
||||
const receipt = [];
|
||||
|
||||
while (sellerPreference.length > 0 && wantedQuantity > 0) {
|
||||
const seller = sellerPreference.shift();
|
||||
|
||||
const r = this.market.sellers[seller.index].sell(product, wantedQuantity);
|
||||
wantedQuantity = (wantedQuantity - r.boughtQuantity) < 0 ? 0 : (wantedQuantity - r.boughtQuantity);
|
||||
|
||||
receipt.push(r);
|
||||
}
|
||||
|
||||
if (receipt.length > 1)
|
||||
total = receipt.reduce((a, cv) => {
|
||||
return (typeof a === 'number' ? a : a.cost) + cv.cost;
|
||||
});
|
||||
else if (receipt.length === 1) total = receipt[0].cost;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should optimise price when filling an order
|
||||
* if the quantity is greater than any single seller can accomodate
|
||||
* then the next cheapest seller should be used.
|
||||
*/
|
||||
fillWithBestPrices(product, quantity) {
|
||||
const sellerPreference = this.buildOrderPrefBestPrice(product);
|
||||
|
||||
return this.doPurchases(sellerPreference, product, quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should optimise for sellers with the largest inventory when filling an order
|
||||
* if the quantity is greater than any single seller can accomodate
|
||||
* then the next largest seller should be used.
|
||||
* if multiple sellers have the same amount of inventory
|
||||
* you should use the cheaper of the two.
|
||||
*/
|
||||
fillWithLargestSellers(product, quantity) {
|
||||
const sellerPreference = this.buildOrderPrefMostFill(product);
|
||||
|
||||
return this.doPurchases(sellerPreference, product, quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This fulfils orders based on time to deliver
|
||||
* @param product
|
||||
* @param quantity
|
||||
* @returns {number}
|
||||
*/
|
||||
quicklyFill(product, quantity) {
|
||||
const sellerPreference = this.buildOrderPrefFastFill(product);
|
||||
|
||||
return this.doPurchases(sellerPreference, product, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Buyer };
|
20
Market.js
Normal file
20
Market.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { Subject, interval } = require('rxjs');
|
||||
|
||||
class Market {
|
||||
constructor(sellers) {
|
||||
this.sellers = sellers;
|
||||
this.observable = new Subject();
|
||||
this.observable.subscribe({
|
||||
next: (v) => this.tick()
|
||||
});
|
||||
interval(5000).subscribe(v => this.observable.next(v));
|
||||
}
|
||||
|
||||
tick(){
|
||||
|
||||
this.sellers.forEach(seller => {
|
||||
seller.tick()
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.Market = Market;
|
71
Seller.js
Normal file
71
Seller.js
Normal file
@ -0,0 +1,71 @@
|
||||
const stream = require('stream');
|
||||
const rand = require('random-seed');
|
||||
|
||||
|
||||
function getExpectedChange(generator) {
|
||||
return generator(100) / 100;
|
||||
}
|
||||
|
||||
function getDeliveries(iProduct, generator) {
|
||||
let fluctuation = getExpectedChange(generator);
|
||||
let newDeliveries = fluctuation * iProduct.startingQuantity;
|
||||
iProduct.quantity += iProduct.quantity + newDeliveries;
|
||||
return iProduct;
|
||||
}
|
||||
|
||||
class Seller {
|
||||
constructor(inventory, id = "Safeway", deliveryWait = 5) {
|
||||
this.inventory = inventory;
|
||||
this.deliveryWait = deliveryWait;
|
||||
this.random_generator = rand(id);
|
||||
this.id = id;
|
||||
for (let [key, value] of Object.entries(inventory)) {
|
||||
value.startingQuantity = value.quantity;
|
||||
value.priceHistory = [value.price];
|
||||
value.stingyness = 0;
|
||||
}
|
||||
}
|
||||
quote(product) {
|
||||
const inventory = this.inventory[product];
|
||||
return inventory.price;
|
||||
}
|
||||
|
||||
calculatePriceChange(product){
|
||||
const inventory = this.inventory[product];
|
||||
const v = 0.1
|
||||
const ec = getExpectedChange(this.random_generator);
|
||||
const alpha = inventory.startingQuantity
|
||||
const beta = inventory.quantity
|
||||
// console.log(`${this.id} alpha: ${alpha} // beta: ${beta} // ec: ${ec}`);
|
||||
const inv_based_change = Math.log10(beta / alpha) * (-v);
|
||||
const sentimentChange = inv_based_change + ((ec - 0.5)*v)
|
||||
return sentimentChange;
|
||||
}
|
||||
|
||||
sell(product, buyQuantity) {
|
||||
const inventory = this.inventory[product];
|
||||
const boughtQuantity = buyQuantity > inventory.quantity ? inventory.quantity : buyQuantity;
|
||||
const cost = boughtQuantity * this.quote(product);
|
||||
inventory.quantity -= boughtQuantity;
|
||||
inventory.stingyness = 1 - inventory.quantity / inventory.startingQuantity;
|
||||
this.tick();
|
||||
return {boughtQuantity, cost};
|
||||
}
|
||||
|
||||
|
||||
tick() {
|
||||
for (let [product, value] of Object.entries(this.inventory)) {
|
||||
let inventory = value;
|
||||
const isReadyForDelivery = (inventory.priceHistory.length % this.deliveryWait) == 0;
|
||||
if (isReadyForDelivery) {
|
||||
inventory = getDeliveries(inventory, this.random_generator);
|
||||
}
|
||||
let chg = this.calculatePriceChange(product);
|
||||
inventory.price = inventory.price + (inventory.price*chg)
|
||||
inventory.priceHistory.push(inventory.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {Seller}
|
27
main.js
Normal file
27
main.js
Normal file
@ -0,0 +1,27 @@
|
||||
const {asda,costco,budgens} = require("./marketplace");
|
||||
const {Market} = require("./Market");
|
||||
const { Buyer } = require("./Buyer");
|
||||
|
||||
|
||||
function main(){
|
||||
const market = new Market([asda,budgens,costco]);
|
||||
let buyer = new Buyer(market);
|
||||
let product = "Apples";
|
||||
let quantity = 10;
|
||||
buyerFunctions(product, quantity, buyer);
|
||||
observeMarket(market);
|
||||
};
|
||||
|
||||
function buyerFunctions(product, quantity, buyer){
|
||||
console.log(`The best price for ${product} is ${buyer.getBestPrice(product)}`) ;
|
||||
console.log(`To completely fill a order of ${quantity} ${product} costs ${buyer.fillWithBestPrices(product,quantity)}`) ;
|
||||
console.log(`To buy as quickly as possible ${quantity} ${product} costs ${buyer.quicklyFill(product,quantity)}`) ;
|
||||
|
||||
}
|
||||
|
||||
function observeMarket(market){
|
||||
market.observable.subscribe( (mkt) => {
|
||||
console.log(`The current price of apples are ${market.sellers[0].inventory["Apples"].price}`)});
|
||||
}
|
||||
|
||||
main();
|
69
marketplace.js
Normal file
69
marketplace.js
Normal file
@ -0,0 +1,69 @@
|
||||
const { Market } = require("./Market");
|
||||
const { Seller } = require("./Seller")
|
||||
|
||||
const asda = new Seller({
|
||||
"Apples":{
|
||||
quantity:100,
|
||||
price:5.25
|
||||
},
|
||||
"Oranges":{
|
||||
quantity:150,
|
||||
price:8.0
|
||||
},
|
||||
"Pears":{
|
||||
quantity:10,
|
||||
price:15.0
|
||||
},
|
||||
"Banannas":{
|
||||
quantity:1000,
|
||||
price:10.0
|
||||
}
|
||||
}, "Asda", 5);
|
||||
|
||||
const budgens = new Seller({
|
||||
"Apples":{
|
||||
quantity:25,
|
||||
price:4.25
|
||||
},
|
||||
"Oranges":{
|
||||
quantity:15,
|
||||
price:6.0
|
||||
},
|
||||
"Grapes":{
|
||||
quantity:10,
|
||||
price:21.0
|
||||
},
|
||||
"Banannas":{
|
||||
quantity:100,
|
||||
price:4.0
|
||||
}
|
||||
}, "Budgens", 1);
|
||||
|
||||
const costco = new Seller({
|
||||
"Apples":{
|
||||
quantity:250,
|
||||
price:6.25
|
||||
},
|
||||
"Oranges":{
|
||||
quantity:300,
|
||||
price:10.0
|
||||
},
|
||||
"Grapes":{
|
||||
quantity:100,
|
||||
price:35.0
|
||||
},
|
||||
"Pears":{
|
||||
quantity:100,
|
||||
price:30.0
|
||||
},
|
||||
"Mangosteen":{
|
||||
quantity:10,
|
||||
price:100.0
|
||||
},
|
||||
"Banannas":{
|
||||
quantity:100,
|
||||
price:8.0
|
||||
}
|
||||
}, "Costco", 10);
|
||||
|
||||
module.exports = {asda,budgens, costco}
|
5162
package-lock.json
generated
Normal file
5162
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "codingtest",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node main.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"eslint": "^7.6.0",
|
||||
"expect": "^26.0.1",
|
||||
"jest": "^26.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"random-seed": "^0.3.0",
|
||||
"rxjs": "^6.5.5"
|
||||
}
|
||||
}
|
63
tests/buyer.test.js
Normal file
63
tests/buyer.test.js
Normal file
@ -0,0 +1,63 @@
|
||||
const expect = require('expect');
|
||||
const { asda, costco, budgens } = require('../marketplace');
|
||||
const { Market } = require('../Market');
|
||||
const { Buyer } = require('../Buyer');
|
||||
|
||||
describe('Buyer', function () {
|
||||
var market;
|
||||
beforeEach(() => {
|
||||
market = new Market([asda, budgens, costco]);
|
||||
});
|
||||
|
||||
// getBestPrice
|
||||
|
||||
it('should return best price', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.getBestPrice('Apples')).toEqual(4.25);
|
||||
});
|
||||
|
||||
it('should return best price that only 2 have', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.getBestPrice('Grapes')).toEqual(21);
|
||||
});
|
||||
|
||||
it('should return best price that only 1 has', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.getBestPrice('Mangosteen')).toEqual(100.0);
|
||||
});
|
||||
|
||||
it('should return 0 when fruit doesnt exist', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.getBestPrice('Kumquat')).toEqual(0);
|
||||
});
|
||||
|
||||
// Filling
|
||||
it('fill 10 apples', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.fillWithBestPrices('Apples', 10)).toEqual(42.5);
|
||||
});
|
||||
|
||||
it('fill 50 apples', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.fillWithBestPrices('Apples', 50)).toEqual(233.60268569487857);
|
||||
});
|
||||
|
||||
it('unable to fill 10 Kumquat', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.fillWithBestPrices('Kumquat', 10)).toEqual(0);
|
||||
});
|
||||
|
||||
// Large fill
|
||||
it('Large fill 50 apples', () => {
|
||||
const buyer = new Buyer(market);
|
||||
|
||||
expect(buyer.fillWithLargestSellers('Apples', 50)).toEqual(312.5);
|
||||
});
|
||||
});
|
100
tests/seller.test.js
Normal file
100
tests/seller.test.js
Normal file
@ -0,0 +1,100 @@
|
||||
const expect = require("expect")
|
||||
const {Seller} = require("../Seller")
|
||||
|
||||
|
||||
describe("Seller", function(){
|
||||
var sellerInventory;
|
||||
beforeEach(() => {
|
||||
sellerInventory = {
|
||||
"Apples":{
|
||||
quantity:100,
|
||||
price:5.25
|
||||
},
|
||||
"Oranges":{
|
||||
quantity:150,
|
||||
price:8.0
|
||||
},
|
||||
"Pears":{
|
||||
quantity:10,
|
||||
price:15.0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("should reduce quantity when i sell", ()=> {
|
||||
let sut = new Seller(sellerInventory)
|
||||
sut.sell("Apples", 25)
|
||||
expect(sut.inventory["Apples"].quantity).toEqual(75)
|
||||
});
|
||||
|
||||
it("should cap at 0 if I sell more than i have", () =>{
|
||||
let sut = new Seller(sellerInventory);
|
||||
sut.sell("Apples", 105);
|
||||
expect(sut.inventory["Apples"].quantity).toEqual(0);
|
||||
});
|
||||
|
||||
it('should be maximally stingy when empty inventory', () => {
|
||||
let sut = new Seller(sellerInventory);
|
||||
sut.sell("Apples", 105);
|
||||
expect(sut.inventory["Apples"].stingyness).toEqual(1);
|
||||
});
|
||||
|
||||
it("should be minimally stingy when full inventory", () => {
|
||||
let sut = new Seller(sellerInventory);
|
||||
expect(sut.inventory["Apples"].stingyness).toEqual(0);
|
||||
});
|
||||
|
||||
it("should be somewhat stingy when half inventory", () => {
|
||||
let sut = new Seller(sellerInventory);
|
||||
sut.sell("Apples", sut.inventory["Apples"].quantity/2);
|
||||
expect(sut.inventory["Apples"].stingyness).toEqual(0.5);
|
||||
});
|
||||
|
||||
it("should quote initial price on first ask", ()=>{
|
||||
let sut = new Seller(sellerInventory);
|
||||
expect(sut.inventory["Oranges"].price).toEqual(8.0);
|
||||
});
|
||||
|
||||
it("should raise prices after seller buys", () =>{
|
||||
let sut = new Seller(sellerInventory, "Kwiksave");
|
||||
sut.sell("Oranges", sut.inventory["Oranges"].quantity/2);
|
||||
expect(sut.inventory["Oranges"].price).toBeGreaterThan(8.0);
|
||||
});
|
||||
|
||||
it("should get deliveries after seller buys once", () =>{
|
||||
const deliveryCadence = 1;
|
||||
let sut = new Seller(sellerInventory,"Asda",deliveryCadence);
|
||||
sut.sell("Oranges", 1);
|
||||
expect(sut.inventory["Oranges"].quantity).toBeGreaterThan(sut.inventory["Oranges"].startingQuantity);
|
||||
});
|
||||
|
||||
it("should be able to set delivery schedule", () => {
|
||||
const deliveryCadence = 3;
|
||||
let sut = new Seller(sellerInventory,"Asda",deliveryCadence);
|
||||
allOranges = sut.inventory["Oranges"].quantity;
|
||||
sut.sell("Oranges", allOranges);
|
||||
expect(sut.inventory["Oranges"].quantity).toEqual(0);
|
||||
sut.tick("Oranges");
|
||||
expect(sut.inventory["Oranges"].quantity).toEqual(0);
|
||||
sut.tick("Oranges");
|
||||
expect(sut.inventory["Oranges"].quantity).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should return correct receipt when seller sells single unit", () =>{
|
||||
let sut = new Seller(sellerInventory);
|
||||
const buyAmount = 10;
|
||||
let receipt = sut.sell("Oranges", buyAmount);
|
||||
expect(receipt.cost).toEqual(sut.inventory["Oranges"].priceHistory[0] * buyAmount);
|
||||
expect(receipt.boughtQuantity).toEqual(buyAmount);
|
||||
})
|
||||
|
||||
it("should return correct receipt when seller sells all stock", () =>{
|
||||
let sut = new Seller(sellerInventory);
|
||||
const overboughtAmount = 1000;
|
||||
const expectedBuyAmount = sut.inventory["Oranges"].startingQuantity;
|
||||
let receipt = sut.sell("Oranges", overboughtAmount);
|
||||
expect(receipt.cost).toEqual(sut.inventory["Oranges"].priceHistory[0] * expectedBuyAmount);
|
||||
expect(receipt.boughtQuantity).toEqual(expectedBuyAmount);
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user