Skip to content
Open
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
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ clasp login
```
3. コマンド実行後に表示されたURLにアクセスしgoogleログインします。
4. ログイン後、リダイレクトされたURLをコピーします。
5. 別のターミナルでコンテナの中に入り、curlコマンドでリダイレクトされたURLにアクセスします。
5. 元のターミナルでコンテナの中に入り、curlコマンドでリダイレクトされたURLにアクセスします。
```shell
curl http://localhost:xxxx/?code=xxx
```
Expand All @@ -42,4 +42,21 @@ yarn build
### 8. Google App script に push する
```
yarn deploy
```
```

## テストの実行方法

### 1. テストを実行する
```shell
docker exec -it gemmini-ai-custom-function yarn test
```

### 2. テストカバレッジを確認する
```shell
docker exec -it gemmini-ai-custom-function yarn test:coverage
```

### 3. テストを監視モードで実行する
```shell
docker exec -it gemmini-ai-custom-function yarn test:watch
```
24 changes: 24 additions & 0 deletions app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': ['ts-jest', {
useESM: true,
}],
},
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
extensionsToTreatAsEsm: ['.ts'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/__tests__/**',
],
coverageDirectory: 'coverage',
globals: {
UrlFetchApp: {}, // モック用のグローバルオブジェクト
},
};
10 changes: 8 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
"lint:eslint": "eslint --ext \".js,.ts,.html\"",
"fix:eslint": "npm run lint:eslint --fix",
"prettier": "prettier --write .",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"@google/generative-ai": "^0.2.1",
"@types/google-apps-script": "^1.0.82",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.20",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
Expand All @@ -32,11 +35,14 @@
"eslint-plugin-unused-imports": "^3.1.0",
"gas-webpack-plugin": "^2.5.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"prettier": "^3.2.5",
"terser-webpack-plugin": "^5.3.10",
"ts-jest": "^29.1.2",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4"
}
}
}
82 changes: 82 additions & 0 deletions app/src/lib/__tests__/fetcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import fetcher from '../fetcher';

// モックの型定義
type MockResponse = {
getContentText: jest.Mock;
};

// グローバルモックの設定
global.UrlFetchApp = {
fetch: jest.fn(),
} as unknown as typeof UrlFetchApp;

describe('fetcher', () => {
// 各テスト前にモックをリセット
beforeEach(() => {
jest.clearAllMocks();
});

it('正常系: 正しいJSONレスポンスを返すこと', () => {
// モックレスポンスの準備
const mockJsonResponse = { data: 'test data' };
const mockResponse: MockResponse = {
getContentText: jest.fn().mockReturnValue(JSON.stringify(mockJsonResponse)),
};

// UrlFetchApp.fetchのモック実装
(global.UrlFetchApp.fetch as jest.Mock).mockReturnValue(mockResponse);

// テスト対象の関数を実行
const url = 'https://example.com/api';
const options = { method: 'GET' } as GoogleAppsScript.URL_Fetch.URLFetchRequestOptions;
const result = fetcher<typeof mockJsonResponse>(url, options);

// 検証
expect(global.UrlFetchApp.fetch).toHaveBeenCalledWith(url, options);
expect(mockResponse.getContentText).toHaveBeenCalledWith('UTF-8');
expect(result).toEqual(mockJsonResponse);
});

it('異常系: UrlFetchApp.fetchがエラーを投げた場合、エラーをラップして再スローすること', () => {
// UrlFetchApp.fetchのモック実装でエラーをスロー
const mockError = new Error('Network error');
(global.UrlFetchApp.fetch as jest.Mock).mockImplementation(() => {
throw mockError;
});

// テスト対象の関数を実行し、エラーをキャッチ
const url = 'https://example.com/api';
const options = { method: 'GET' } as GoogleAppsScript.URL_Fetch.URLFetchRequestOptions;

// エラーがスローされることを検証
expect(() => {
fetcher(url, options);
}).toThrow(`Error: ${mockError}`);

// UrlFetchApp.fetchが呼ばれたことを検証
expect(global.UrlFetchApp.fetch).toHaveBeenCalledWith(url, options);
});

it('異常系: JSONのパースに失敗した場合、エラーをラップして再スローすること', () => {
// モックレスポンスの準備(不正なJSON)
const mockResponse: MockResponse = {
getContentText: jest.fn().mockReturnValue('invalid json'),
};

// UrlFetchApp.fetchのモック実装
(global.UrlFetchApp.fetch as jest.Mock).mockReturnValue(mockResponse);

// テスト対象の関数を実行し、エラーをキャッチ
const url = 'https://example.com/api';
const options = { method: 'GET' } as GoogleAppsScript.URL_Fetch.URLFetchRequestOptions;

// エラーがスローされることを検証
expect(() => {
fetcher(url, options);
}).toThrow('Error:');

// 検証
expect(global.UrlFetchApp.fetch).toHaveBeenCalledWith(url, options);
expect(mockResponse.getContentText).toHaveBeenCalledWith('UTF-8');
});
});