Skip to content

Commit

Permalink
Add test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
khattori committed Nov 2, 2024
1 parent ff6e0d1 commit 3d4957b
Show file tree
Hide file tree
Showing 16 changed files with 2,298 additions and 999 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/node_modules
oclif.manifest.json
*.tsbuildinfo
coverage


yarn.lock
Expand Down
2,898 changes: 2,008 additions & 890 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@types/mime-types": "^2.1.4",
"@types/mocha": "^10",
"@types/node": "^18",
"c8": "^10.1.2",
"chai": "^4",
"eslint": "^8",
"eslint-config-oclif": "^5",
Expand Down Expand Up @@ -71,7 +72,7 @@
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "npm run lint",
"prepack": "oclif manifest && oclif readme",
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
"test": "c8 mocha --forbid-only \"test/**/*.test.ts\"",
"version": "oclif readme && git add README.md"
},
"types": "dist/index.d.ts"
Expand Down
11 changes: 7 additions & 4 deletions src/commands/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path'
import * as KeClient from '../client.js'
import {checkCwd, checkInsecure} from '../common.js'
import Config from '../config.js'
import {DIRECTORY_TYPE} from '../const.js'
import {DIRECTORY_TYPE, TABLE_TYPE} from '../const.js'
import {common as commonFlags} from '../flags.js'


Expand Down Expand Up @@ -50,11 +50,14 @@ export default class Ls extends Command {
}

private _printObj(target: KeClient.ObjectResponse, indent: string, longFormat?: boolean) {
if (!target.abspath) {
return
}

const {name} = path.parse(target.abspath)
if (longFormat) {
const {name} = path.parse(target.abspath)
this.log(`${indent}${target.owner} ${target.updated} ${name}:${target.type_object}`)
} else {
const {name} = path.parse(target.abspath)
this.log(`${indent}${name}`)
}
}
Expand All @@ -66,7 +69,7 @@ export default class Ls extends Command {
return false
}

if (target.type_object === DIRECTORY_TYPE) {
if ([DIRECTORY_TYPE, TABLE_TYPE].includes(target.type_object)) {
const resp = await KeClient.getChildren(this.conf, targetPath)
let indent = ''
if (withHeader) {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/mkdir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ export default class Mkdir extends Command {
let result = await KeClient.get(this.conf, parentDir)
if (result === null) {
if (!options.parent) {
throw new Error(`cannot create directory: parent directory '${parentDir}' is not found`)
this.error(`cannot create directory: parent directory '${parentDir}' is not found`)
}

const {base, dir} = path.parse(parentDir)
result = await this.makeDir(dir, base, options)
}

if (result!.type_object !== DIRECTORY_TYPE) {
throw new Error(`cannot create directory: parent directory '${parentDir}' is not directory`)
this.error(`cannot create directory: parent directory '${parentDir}' is not directory`)
}

const data = {
Expand Down
150 changes: 67 additions & 83 deletions src/commands/put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,92 +55,74 @@ export default class Put extends Command {
this.conf = new Config(flags)
checkInsecure(flags.insecure)
const cwd = await checkCwd(this.conf, flags.cwd)
const destPath = path.normalize(path.join(cwd, flags.dest || ''))
const sources = await this._checkSources(argv as string[], path.resolve('.'))

if (sources.length === 0) {
return
}
const destPath = path.resolve(cwd, flags.dest || '')
const sources = await this._checkSources(argv as string[], path.resolve('.'), flags.recursive)
let result = false
if (argv.length > 1) {
const destObj = await KeClient.get(this.conf, destPath)
if (destObj === null || destObj.type_object !== DIRECTORY_TYPE) {
// source が複数で destObj が存在しない場合はコピーできない
this.error(`failed to put '${destPath}': no such object or directory`)
}

const destObj = await KeClient.get(this.conf, destPath)

if (argv.length === 1) {
// destObj/ 配下にsources をコピー
result = (await Promise.all(
sources.map(async (src) => this.putObj(src, destObj, src.name, flags))
)).every(Boolean)
} else if (sources.length === 1) { // argv.length === 1
const source = sources[0]
if (destObj === null) {
// destObj が存在しない場合、親ディレクトリが存在するかチェック
const destObj = await KeClient.get(this.conf, destPath)
if (destObj && destObj.type_object === DIRECTORY_TYPE) {
result = await this.putObj(source, destObj, source.name, flags)
} else {
// destObj が存在しないか、ディレクトリ以外の場合、親ディレクトリが存在するかチェック
const {dir, name} = path.parse(destPath)
const parentDir = await KeClient.get(this.conf, dir)
if (parentDir === null || parentDir.type_object !== DIRECTORY_TYPE) {
this.error(`failed to put '${destPath}': no such object or directory`)
}

await this.putObj(source, parentDir, name, flags)
} else if (destObj.type_object === DIRECTORY_TYPE) {
await this.putObj(source, destObj, source.name, flags)
}
} else {
if (destObj === null || destObj.type_object !== DIRECTORY_TYPE) {
// source が複数で destObj が存在しない場合はコピーできない
this.error(`failed to put '${destPath}': no such object or directory`)
result = await this.putObj(source, parentDir, name, flags)
}
}

// destObj/ 配下にsources をコピー
await Promise.all(
sources.map((src) => this.putObj(src, destObj, src.name, flags))
)
if (!result || sources.length !== argv.length) {
this.exit(1)
}
}

private async _checkSources(sources: string[], cwd: string): Promise<SourceDesc[]> {
const srcDescs: SourceDesc[] = []
private async _checkSources(sources: string[], cwd: string, recursive: boolean): Promise<SourceDesc[]> {
const results: SourceDesc[] = []
await Promise.all(
sources.map(async (src) => {
const source = path.normalize(path.join(cwd, src))
const source = path.resolve(cwd, src)
try {
const stat = await fs.stat(source)
const {ext, name} = path.parse(source)
if (stat.isDirectory()) {
srcDescs.push({ext, name, path: source, type: 'dir'})
if (recursive) {
results.push({ext, name, path: source, type: 'dir'})
} else {
this.warn(`put: -r not specified; omitting directory '${source}'`)
}
} else if (stat.isFile()) {
srcDescs.push({ext, name, path: source, type: 'file'})
results.push({ext, name, path: source, type: 'file'})
} else {
this.log(`put: invalid source '${source} type: should be file or directory`)
this.warn(`put: invalid source '${source}': should be file or directory`)
}
} catch (error) {
if (error instanceof Error) {
this.log(`put: cannot stat '${source}': ${error.message}`)
} else {
this.log(`put: cannot stat '${source}': Unknown error`)
this.warn(`put: cannot stat '${source}': ${error.message}`)
}
}
})
)

return srcDescs
return results
}

private async _createObj(source: SourceDesc, destDirObj: KeClient.ObjectResponse, name: string, verbose?: boolean) {
let fields = {}
let typeObject = ''
if (source.type === 'dir') {
// ディレクトリを新規作成して、再帰的に処理を続ける
typeObject = DIRECTORY_TYPE
} else if (source.type === 'file') {
// ソースの種類に応じてオブジェクトを新規作成する
const dataFields = {
'contentType': mimeLookup(source.path),
'ext': source.ext.replace(/^\.+/, ''),
}
const buf = await fs.readFile(source.path)
if (isText(source.path, buf)) {
fields = {...dataFields, text: buf}
typeObject = TEXT_TYPE
} else {
fields = {...dataFields, data: buf.toString('base64')}
typeObject = BIN_TYPE
}
}

const {fields, typeObject} = source.type === 'file' ? await this._loadSourceFile(source.path, source.ext) : {fields: {}, typeObject: DIRECTORY_TYPE}
const result = await KeClient.create(this.conf, destDirObj.abspath, {fields, name, 'type_object': typeObject})
if (verbose) {
this.log(`created object (${result.type_object}): ${result.abspath}`)
Expand All @@ -149,34 +131,37 @@ export default class Put extends Command {
return result
}

private async _updateObj(source: SourceDesc, destObj: KeClient.ObjectResponse, verbose?: boolean) {
private async _loadSourceFile(srcPath: string, ext: string) {
const dataFields = {
contentType: mimeLookup(source.path),
ext: source.ext.replace(/^\.+/, '')
contentType: mimeLookup(srcPath),
ext: ext.replace(/^\.+/, ''),
}
const buf = await fs.readFile(source.path)
const istext = isText(source.path, buf)
let data = {}
if (istext && destObj.type_object === TEXT_TYPE) {
data = {
'fields': {...dataFields, text: buf},
}
} else if (!istext && destObj.type_object === BIN_TYPE) {
data = {
'fields': {...dataFields, data: buf.toString('base64')},
}
} else {
this.log(`put: cannot overwrite ${destObj.abspath}: type '${destObj.type_object}' mismatch`)
return
const buf = await fs.readFile(srcPath)
return isText(srcPath, buf) ? {
fields: {...dataFields, text: buf.toString('utf8')},
typeObject: TEXT_TYPE
} : {
fields: {...dataFields, data: buf.toString('base64')},
typeObject: BIN_TYPE
}
}

private async _updateObj(source: SourceDesc, destObj: KeClient.ObjectResponse, verbose?: boolean) {
const {fields, typeObject} = source.type === 'dir' ? {fields: {}, typeObject: DIRECTORY_TYPE} : await this._loadSourceFile(source.path, source.ext)
if (typeObject !== destObj.type_object) {
this.warn(`put: cannot overwrite ${destObj.abspath}: type '${destObj.type_object}' mismatch`)
return false
}

await KeClient.update(this.conf, destObj.abspath, data)
await KeClient.update(this.conf, destObj.abspath, {fields})
if (verbose) {
this.log(`updated object (${destObj.type_object}): ${destObj.abspath}`)
}

return true
}

private async putObj(source: SourceDesc, destDir: KeClient.ObjectResponse, name: string, options: PutOptions) {
private async putObj(source: SourceDesc, destDir: KeClient.ObjectResponse, name: string, options: PutOptions): Promise<boolean> {
//
// putObj:
// source からファイルやディレクトリを読み込み、dest 配下/に target オブジェクトとして配置する
Expand All @@ -187,25 +172,24 @@ export default class Put extends Command {
// - name: 新規作成時のオブジェクト名称
// - options: Putオプション
//
let result = true
let destObj = await KeClient.get(this.conf, path.join(destDir.abspath, name))
if (destObj === null) {
destObj = await this._createObj(source, destDir, name, options.verbose)
} else if (destObj && options.overwrite && source.type === 'file' && [BIN_TYPE, TEXT_TYPE].includes(destObj.type_object)) {
await this._updateObj(source, destObj, options.verbose)
} else if (destObj && options.overwrite && !(source.type === 'dir' && destObj.type_object === DIRECTORY_TYPE)) {
// 配置先オブジェクトとデータソースの種類がミスマッチの場合
this.log(`put: cannot overwrite ${destObj.abspath}: type '${destObj.type_object}' mismatch`)
return
} else if (destObj && options.overwrite) {
result = await this._updateObj(source, destObj, options.verbose)
}

if (options.recursive && destObj.type_object === DIRECTORY_TYPE) {
// ファイル一覧を取得
const dirs = await fs.readdir(source.path)
const sources = await this._checkSources(dirs, source.path)
const sources = await this._checkSources(dirs, source.path, true)

await Promise.all(
sources.map((src) => this.putObj(src, destObj, src.name, options))
)
return (await Promise.all(
sources.map(async (src) => this.putObj(src, destObj, src.name, options))
)).every(Boolean) && result
}

return result
}
}
11 changes: 8 additions & 3 deletions src/commands/rm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,33 @@ export default class Rm extends Command {
this.conf = new Config(flags)
checkInsecure(flags.insecure)
const cwd = await checkCwd(this.conf, flags.cwd)
await Promise.all(
const results = await Promise.all(
argv.map((arg) => this.removeObject(path.resolve(cwd, arg as string), flags))
)
if (results.includes(false)) {
this.exit(1)
}
}

private async removeObject(target: string, options: RmOptions) {
if (!options.force) {
const result = await KeClient.get(this.conf, target)
if (result === null) {
this.warn(`failed to remove: '${target}' is not found`)
return
return false
}

if (result.type_object === DIRECTORY_TYPE) {
this.warn(`failed to remove: '${target}' is a directory`)
return
return false
}
}

const removed = await KeClient.del(this.conf, target, options.force)
if (options.verbose && removed) {
this.log(`removed: ${target}`)
}

return true
}
}
11 changes: 7 additions & 4 deletions src/commands/rmdir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ export default class Rmdir extends Command {
checkInsecure(flags.insecure)
const cwd = await checkCwd(this.conf, flags.cwd)

await Promise.all(
const results = await Promise.all(
argv.map((arg) => this.removeDirectory(cwd, arg as string, flags))
)
if (results.includes(null)) {
this.exit(1)
}
}

private async removeDirectory(cwd: string, target: string, options: RmdirOptions) {
Expand All @@ -57,19 +60,19 @@ export default class Rmdir extends Command {

if (result === null) {
this.warn(`failed to remove directory: '${targetDir}' is not found`)
return
return null
}

if (result.type_object !== DIRECTORY_TYPE) {
this.warn(`failed to remove directory: '${targetDir}' is not a directory`)
return
return null
}

// ディレクトリが空かどうかチェックする
const results = await KeClient.getChildren(this.conf, targetDir)
if (results!.count > 0) {
this.warn(`failed to remove directory: '${targetDir}' is not empty`)
return
return null
}

await KeClient.del(this.conf, targetDir)
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class Config {
baseurl?: string
token?: string

constructor(flags: ConfigOptions) {
constructor(flags: ConfigOptions = {}) {
const config = require('config')

this.baseurl = flags.baseurl || config.get('baseurl')
Expand Down
1 change: 1 addition & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const BIN_TYPE = '/system/types/Binary'
export const DIRECTORY_TYPE = '/system/types/Directory'
export const TABLE_TYPE = '/system/types/Table'
export const TEXT_TYPE = '/system/types/Text'
Loading

0 comments on commit 3d4957b

Please sign in to comment.