Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl: APK command #14

Merged
merged 4 commits into from
Jan 4, 2023
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
6 changes: 4 additions & 2 deletions src/command/apk.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {ApkCompareOption} from "@/types/ApkCompareOption";
import Feature from "@/types/Feature";
import ApkInfo from "@/types/ApkInfo";

export default interface Apk {
summary(): string
summary(): ApkInfo

fileSize(): number

downloadSize(): number

features(notRequired: boolean): [string]
features(notRequired: boolean): Feature[]

compare(other: string, option: ApkCompareOption): any
}
26 changes: 21 additions & 5 deletions src/command/impl/ApkDefaultImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {ApkCompareOption} from "@/types/ApkCompareOption";
import Executor from "@/executor/executor";
import {exists, isApk} from "@/util/file-util";
import {FileError, FileErrorType} from "@/types/Error";
import ApkInfo from "@/types/ApkInfo";
import Feature from "@/types/Feature";

export class ApkDefaultImpl implements Apk {
readonly path: string
Expand All @@ -13,11 +15,11 @@ export class ApkDefaultImpl implements Apk {
this.executor = executor
}


summary(): string {
summary(): ApkInfo {
const command = `apkanalyzer apk summary ${this.path}`
const [applicationId, versionCode, versionName] = this.executor.execute(command).split(" ")

return this.executor.execute(command);
return new ApkInfo(applicationId, parseFloat(versionCode), versionName)
}


Expand All @@ -33,8 +35,22 @@ export class ApkDefaultImpl implements Apk {
return parseFloat(this.executor.execute(command))
}

features(notRequired: boolean): [string] {
return [""];
features(notRequired: boolean): Feature[] {
let command = "apkanalyzer apk -h features"
if (notRequired) {
command += " --not-required"
}
command += ` ${this.path}`

return this.executor.execute(command)
.replace(/\r\n|\n|\r/g, "\n")
.split("\n")
.map(value => {
const name = value.substring(0, value.indexOf(' '))
const desc = value.substring(value.indexOf(' ') + 1)

return new Feature(name, desc, desc === "not-required");
})
}

compare(other: string, option: ApkCompareOption): any {
Expand Down
2 changes: 1 addition & 1 deletion src/executor/impl/ExecutorDefaultImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import child_process from "child_process";

export default class ExecutorDefaultImpl implements Executor {
execute(command: string): string {
return child_process.execSync(command).toString()
return child_process.execSync(command).toString('utf8')
}
}
20 changes: 20 additions & 0 deletions src/types/ApkInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export type ApplicationId = string
export type VersionCode = number
export type VersionName = string

export default class ApkInfo {
applicationId: ApplicationId
versionCode: VersionCode

versionName: VersionName

constructor(
applicationId: ApplicationId,
versionCode: VersionCode,
versionName: VersionName
) {
this.applicationId = applicationId
this.versionCode = versionCode
this.versionName = versionName
}
}
17 changes: 17 additions & 0 deletions src/types/Feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default class Feature {
name: string

desc: string

notRequired: boolean

constructor(
name: string,
desc: string,
notRequired: boolean
) {
this.name = name
this.desc = desc
this.notRequired = notRequired
}
}
105 changes: 103 additions & 2 deletions test/command/ApkDefaultImpl.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {apkStatus} from "@/index";
import Executor from "@/executor/executor";
import ApkInfo from "@/types/ApkInfo";
import Feature from "@/types/Feature";

describe("ApkDefaultImpl test", () => {
it("test summary", () => {
const executeMock = jest.fn().mockReturnValue("mock");
const executeMock = jest.fn().mockReturnValue("com.ryunen344.fixture 1 1.0.0");
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));
Expand All @@ -12,10 +14,109 @@ describe("ApkDefaultImpl test", () => {
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
status.apk.summary()
const actual = status.apk.summary()

expect(executeMock).toBeCalledWith("apkanalyzer apk summary test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toStrictEqual(new ApkInfo("com.ryunen344.fixture", 1, "1.0.0"))
})

it("test fileSize", () => {
const executeMock = jest.fn().mockReturnValue("32453241");
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));

const status = apkStatus({
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
const actual = status.apk.fileSize()

expect(executeMock).toBeCalledWith("apkanalyzer apk file-size test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toBe(32453241)
})

it("test downloadSize", () => {
const executeMock = jest.fn().mockReturnValue("32453241");
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));

const status = apkStatus({
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
const actual = status.apk.downloadSize()

expect(executeMock).toBeCalledWith("apkanalyzer apk download-size test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toBe(32453241)
})

describe("test features", () => {
it("given not required false", () => {
const executeMock = jest.fn().mockReturnValue("android.hardware.faketouch implied: default feature for all apps");
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));

const status = apkStatus({
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
const actual = status.apk.features(false)

expect(executeMock).toBeCalledWith("apkanalyzer apk -h features test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toStrictEqual([new Feature("android.hardware.faketouch", "implied: default feature for all apps", false)])
})

it("given not required true", () => {
const executeMock = jest.fn().mockReturnValue("android.hardware.camera not-required");
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));

const status = apkStatus({
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
const actual = status.apk.features(true)

expect(executeMock).toBeCalledWith("apkanalyzer apk -h features --not-required test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toStrictEqual([
new Feature("android.hardware.camera", "not-required", true)
])
})

it("given linebreaks", () => {
const executeMock = jest.fn().mockReturnValue(
"crlf linebreaks\r\n" +
"lf linebreaks\r" +
"cr linebreaks\n" +
"last linebreaks"
);
const MockExecutor = jest.fn<Executor, []>().mockImplementation(() => ({
execute: executeMock
}));

const status = apkStatus({
path: "test/__resource__/empty.apk",
executor: new MockExecutor()
})
const actual = status.apk.features(true)

expect(executeMock).toBeCalledWith("apkanalyzer apk -h features --not-required test/__resource__/empty.apk")
expect(executeMock).toBeCalledTimes(1);
expect(actual).toStrictEqual([
new Feature("crlf", "linebreaks", false),
new Feature("lf", "linebreaks", false),
new Feature("cr", "linebreaks", false),
new Feature("last", "linebreaks", false),
])
})
})
})
15 changes: 15 additions & 0 deletions test/executor/ExecutorDefaultImpl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {execSync} from "child_process";
import ExecutorDefaultImpl from "@/executor/impl/ExecutorDefaultImpl";

jest.mock('child_process')

describe("test default executor", () => {
it("execute should call child process", () => {
(execSync as jest.Mock).mockImplementationOnce(() => "ok");
const executor = new ExecutorDefaultImpl();
const actual = executor.execute("args")

expect(execSync).toHaveBeenCalledWith("args")
expect(actual).toBe("ok")
})
})