Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"build": "cpx 'appsscript.json' 'dist' -v && cpx 'src/ui/*.html' 'dist' -v && webpack",
"watch": "webpack -w",
"lint": "eslint 'src/**/*.ts'",
"test": "npm run lint && jest",
"jest": "jest",
"test": "npm run lint && npm run jest",
"switch:local": "cp .clasp.local.json .clasp.json",
"switch:dev": "cp .clasp.dev.json .clasp.json",
"switch:prod": "cp .clasp.prod.json .clasp.json",
Expand Down
5 changes: 3 additions & 2 deletions src/__mocks__/api/v3/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { default as company } from '~/__mocks__/fixtures/v3/company.js'
import { default as daily } from '~/__mocks__/fixtures/v3/daily.js'
import { default as quarter } from '~/__mocks__/fixtures/v3/quarter.js'
import { HttpError } from '~/api/http-error'
import { Company } from '~/entities/v3/company'
import { Daily } from '~/entities/v3/daily'
import { Quarter } from '~/entities/v3/quarter'

Expand All @@ -17,13 +18,13 @@ export class BuffettCodeApiClientV3 {
this.mockQuarter.mockReturnValue(quarter)
}

company(ticker: string): object {
company(ticker: string): Company {
if (ticker !== '2371') {
const res = new HTTPResnpose()
throw new HttpError('/v3/company', res)
}

return this.mockCompany()['data']
return Company.fromResponse(this.mockCompany())
Comment thread
t2h5 marked this conversation as resolved.
}

quarter(ticker: string): Quarter {
Expand Down
11 changes: 6 additions & 5 deletions src/api/company-service.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { BuffettCodeApiClientV3 } from '~/api/v3/client'
import { Company } from '~/entities/v3/company'
import { DateParam } from '~/fiscal-periods/date-param'
import { LqWithOffset } from '~/fiscal-periods/lq-with-offset'
import { LyWithOffset } from '~/fiscal-periods/ly-with-offset'
import { YearQuarter } from '~/fiscal-periods/year-quarter'
import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param'

export class CompanyService {
public company: object
public company: Company

constructor(public ticker: string, client: BuffettCodeApiClientV3, private today: Date = new Date()) {
this.company = client.company(ticker)
}

public convertYearQuarterParamToYearQuarter(period: YearQuarterParam): YearQuarter {
if (period.year instanceof LyWithOffset) {
period.year = this.company['latest_fiscal_year'] + period.year.offset
period.year = this.company.data['latest_fiscal_year'] + period.year.offset
}

if (period.quarter instanceof LqWithOffset) {
period.year = (period.year as number) + Math.ceil(period.quarter.offset / 4)
period.quarter = this.company['latest_fiscal_quarter'] + (period.quarter.offset % 4)
period.quarter = this.company.data['latest_fiscal_quarter'] + (period.quarter.offset % 4)

if (period.quarter <= 0) {
period.year -= 1
Expand All @@ -38,7 +39,7 @@ export class CompanyService {
period = _period
}

const fixedTierRange = this.company['fixed_tier_range']
const fixedTierRange = this.company.data['fixed_tier_range']
const fixedTierOldestPeriod = new YearQuarter(
fixedTierRange['oldest_fiscal_year'],
fixedTierRange['oldest_fiscal_quarter']
Expand All @@ -51,7 +52,7 @@ export class CompanyService {
return false
}

const fixedTierRange = this.company['fixed_tier_range']
const fixedTierRange = this.company.data['fixed_tier_range']
const fixedTierOldestDate = new Date(fixedTierRange['oldest_date'])

return date.toDate().valueOf() < fixedTierOldestDate.valueOf()
Expand Down
3 changes: 2 additions & 1 deletion src/api/v3/caching-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BuffettCodeApiClientV3 } from '~/api/v3/client'
import { Company } from '~/entities/v3/company'
import { Daily } from '~/entities/v3/daily'
import { Quarter } from '~/entities/v3/quarter'
import { DateParam } from '~/fiscal-periods/date-param'
Expand All @@ -12,7 +13,7 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 {
super(token)
}

company(ticker: string): object {
company(ticker: string): Company {
const cached = CompanyCache.get(ticker)
if (cached) {
return cached
Expand Down
3 changes: 2 additions & 1 deletion src/api/v3/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as quarter from '~/__mocks__/fixtures/v3/quarter'
import { HttpError } from '~/api/http-error'
import { useMockedUrlFetchApp } from '~/api/test-helper'
import { BuffettCodeApiClientV3 } from '~/api/v3/client'
import { Company } from '~/entities/v3/company'
import { Daily } from '~/entities/v3/daily'
import { Quarter } from '~/entities/v3/quarter'
import { DateParam } from '~/fiscal-periods/date-param'
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('BuffettCodeApiClientV3', () => {

const client = new BuffettCodeApiClientV3('foo')
const ticker = '2371'
expect(client.company(ticker)).toEqual(company['data'])
expect(client.company(ticker)).toEqual(Company.fromResponse(company))
expect(mockFetch.mock.calls.length).toBe(1)
expect(mockFetch.mock.calls[0].length).toBe(2)
expect(mockFetch.mock.calls[0][0]).toBe(`https://api.buffett-code.com/api/v3/company?ticker=${ticker}`)
Expand Down
5 changes: 3 additions & 2 deletions src/api/v3/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpError } from '~/api/http-error'
import { UrlBuilder } from '~/api/url-builder'
import { Company } from '~/entities/v3/company'
import { Daily } from '~/entities/v3/daily'
import { Quarter } from '~/entities/v3/quarter'
import { DateParam } from '~/fiscal-periods/date-param'
Expand Down Expand Up @@ -45,14 +46,14 @@ export class BuffettCodeApiClientV3 {
}
}

public company(ticker: string): object {
public company(ticker: string): Company {
const endpoint = BuffettCodeApiClientV3.baseUrl + '/company'
const builder = new UrlBuilder(endpoint, { ticker: ticker })
const url = builder.toString()
const options = this.defaultOptions()

const res = BuffettCodeApiClientV3.request(url, options)
return res['data']
return Company.fromResponse(res)
}

public quarter(ticker: string, period: YearQuarterParam): Quarter {
Expand Down
16 changes: 16 additions & 0 deletions src/custom-functions/v3/bcode-company.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client'
import { bcodeCompany } from '~/custom-functions/v3/bcode-company'
import { BcodeResult } from '~/custom-functions/v3/bcode-result'

jest.mock('~/api/v3/client', () => jest.requireActual('~/__mocks__/api/v3/client'))
jest.mock('~/services/company-cache', () => jest.requireActual('~/__mocks__/services/company-cache'))

test('bcodeCompany', () => {
const ticker = '2371'
const propertyName = 'company_name'

const client = new CachingBuffettCodeApiClientV3('token')
const result = bcodeCompany(client, ticker, propertyName)

expect(result).toEqual(new BcodeResult(propertyName, 'カカクコム', 'なし'))
})
17 changes: 17 additions & 0 deletions src/custom-functions/v3/bcode-company.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BuffettCodeApiClientV3 } from '~/api/v3/client'
import { PropertyNotFoundError } from '~/custom-functions/error'
import { BcodeResult } from '~/custom-functions/v3/bcode-result'

export function bcodeCompany(client: BuffettCodeApiClientV3, ticker: string, propertyName: string): BcodeResult {
const company = client.company(ticker)

const property = company.columnDescription[propertyName]
if (property == undefined) {
throw new PropertyNotFoundError(`propetyName '${propertyName}' is not found.`)
}

const value = company.data[propertyName]
const unit = company.unitOf(propertyName)

return new BcodeResult(propertyName, value, unit)
}
27 changes: 19 additions & 8 deletions src/custom-functions/v3/bcode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { bcodeCompany } from './bcode-company'
import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client'
import { bcodeDaily } from '~/custom-functions/v3/bcode-daily'
import { bcodeQuarter } from '~/custom-functions/v3/bcode-quarter'
Expand All @@ -8,7 +9,7 @@ import { Setting } from '~/setting'

export function bcode(
ticker: string,
period: string | Date,
intent: string | Date,
propertyName: string,
isRawValue = false,
isWithUnits = false
Expand All @@ -17,26 +18,36 @@ export function bcode(
throw new Error('<<tickerが有効ではありません>>')
}

if (!period) {
throw new Error('<<periodが有効ではありません>>')
if (!intent) {
throw new Error('<<intentが有効ではありません>>')
}

if (!propertyName) {
throw new Error('<<propertyNameが有効ではありません>>')
}

if (period instanceof Date) {
period = period.toISOString().substring(0, 10)
}

const setting = Setting.load()
if (!setting.token) {
throw new Error('<<APIキーが有効ではありません>>')
}

if (typeof intent === 'string' && intent.toUpperCase() === Setting.companyIntent) {
try {
const client = new CachingBuffettCodeApiClientV3(setting.token)
const result = bcodeCompany(client, ticker, propertyName)
return result.format(isRawValue, isWithUnits)
} catch (e) {
ErrorHandler.handle(e)
}
}

if (intent instanceof Date) {
intent = intent.toISOString().substring(0, 10)
}

try {
const client = new CachingBuffettCodeApiClientV3(setting.token)
const parsedPeriod = PeriodParser.parse(period)
const parsedPeriod = PeriodParser.parse(intent)
let result: BcodeResult
if (PeriodParser.isDateParam(parsedPeriod)) {
result = bcodeDaily(
Expand Down
18 changes: 18 additions & 0 deletions src/entities/v3/company.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as response from '~/__mocks__/fixtures/v3/company'
import { Company } from '~/entities/v3/company'

const company = Company.fromResponse(response)

test('propertyName', () => {
expect(company.propertyNames()).toEqual(Object.keys(response['column_description']))
})

test('labelOf', () => {
expect(company.labelOf('company_name')).toEqual('会社名')
expect(company.labelOf('priority_market')).toEqual('優先市場')
})

test('unitOf', () => {
expect(company.unitOf('company_name')).toEqual('なし')
expect(company.unitOf('priority_market')).toEqual('なし')
})
37 changes: 37 additions & 0 deletions src/entities/v3/company.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { HasColumnDescription } from '~/entities/v3/interface'

export class Company implements HasColumnDescription {
constructor(readonly data: object, readonly columnDescription: object) {
// noop
}

propertyNames(): string[] {
return Object.keys(this.data)
}

labelOf(propertyName: string): string | null {
const desc = this.columnDescriptionOf(propertyName)
if (desc) {
return desc['name_jp']
} else {
return null
}
}

unitOf(propertyName: string): string | null {
const desc = this.columnDescriptionOf(propertyName)
if (desc) {
return desc['unit']
} else {
return null
}
}

private columnDescriptionOf(propertyName: string): string | null {
return this.columnDescription[propertyName]
}

static fromResponse(response: object): Company {
return new Company(response['data'], response['column_description'])
}
}
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ global.exportCsv = exportCsv
* 指定した銘柄の財務数字や指標を取得します。
*
* @param {"6501"} ticker 銘柄コード
* @param {"2017Q4"} period 会計期間 (例: 四半期 '2017Q4', 日付 '2020-09-06')
* @param {"2017Q4"} intent 会計期間または識別子 (例: 四半期 '2017Q4', 日付 '2020-09-06', 企業情報 'COMPANY')
* @param {"net_sales"} propertyName 項目名
* @param {TRUE} isRawValue (オプション) 数値をRAWデータで表示するかどうか (デフォルト値: FALSE)
* @param {TRUE} isWithUnits (オプション) 単位を末尾に付加するかどうか (デフォルト値: FALSE)
* @param {TRUE} param5 (オプション) 廃止予定のオプション
* @return 指定した銘柄の財務数字または指標
* @customfunction
*/
global.BCODE = (ticker, period, propertyName, isRawValue = false, isWithUnits = false): number | string => {
return bcode(ticker, period, propertyName, isRawValue, isWithUnits)
global.BCODE = (ticker, intent, propertyName, isRawValue = false, isWithUnits = false): number | string => {
return bcode(ticker, intent, propertyName, isRawValue, isWithUnits)
}
37 changes: 27 additions & 10 deletions src/services/company-cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import * as companyFixture from '~/__mocks__/fixtures/v3/company'
import { Company } from '~/entities/v3/company'
import { getMock, putMock } from '~/services/cache-test-helper'
import { CompanyCache } from '~/services/company-cache'

const company = companyFixture['data']
const company = Company.fromResponse(companyFixture)

test('key', () => {
expect(CompanyCache.key('2371')).toBe('company-2371')
})

test('columnDescriptionKey', () => {
expect(CompanyCache.columnDescriptionKey()).toBe('company-column-description')
})

beforeEach(() => {
jest.clearAllMocks()
})

test('get', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、 getput のテストも一応書いてもらえると!
quarterやdailyにテストが存在してないのは追加し忘れか手抜きだと思います… 🙇

大元は getXXXputXXX しかなくて、 getput は後から追加したものです。
getXXXputXXX は移行のためにpublicなメソッドになってますが、おそらく使ってるところはもうないため、順次privateに切り替えていって、 getput だけテストする世界線にした方がわかりやすいかなと。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正してみました
020c0f8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

getMock.mockReturnValueOnce(JSON.stringify(company))
expect(CompanyCache.get('2371')).toEqual(company)
expect(CompanyCache.get('2371')).toBeNull()
describe('get', () => {
test('returns data if cache exists', () => {
getMock.mockReturnValueOnce(JSON.stringify(company.data))
getMock.mockReturnValueOnce(JSON.stringify(company.columnDescription))
expect(CompanyCache.get('2371')).toEqual(company)

expect(getMock).toBeCalledTimes(2)
expect(getMock).nthCalledWith(1, 'company-2371')
expect(getMock).nthCalledWith(2, 'company-column-description')
})

test('returns null if cache does not exist', () => {
getMock.mockReturnValue(null)
expect(CompanyCache.get('2371')).toBeNull()

expect(getMock).toBeCalledTimes(2)
expect(getMock).nthCalledWith(1, 'company-2371')
expect(getMock).nthCalledWith(2, 'company-2371')
expect(getMock).toBeCalledTimes(2)
expect(getMock).nthCalledWith(1, 'company-2371')
expect(getMock).nthCalledWith(2, 'company-column-description')
})
})

test('put', () => {
CompanyCache.put('2371', company)

expect(putMock).toBeCalledTimes(1)
expect(putMock).toBeCalledWith('company-2371', JSON.stringify(company), 21600)
expect(putMock).toBeCalledTimes(2)
expect(putMock).toBeCalledWith('company-2371', JSON.stringify(company.data), 21600)
expect(putMock).toBeCalledWith('company-column-description', JSON.stringify(company.columnDescription), 21600)
})
Loading