Compare commits
10 Commits
ca9658be6b
...
5beab5f38d
Author | SHA1 | Date | |
---|---|---|---|
5beab5f38d | |||
b40bbbca13 | |||
7afe441f8c | |||
0fc7294905 | |||
d2cf8bdea4 | |||
2d393515c4 | |||
918a4eeb52 | |||
ce2921a6c1 | |||
7f2471103b | |||
384c33e6a6 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
book
|
||||
.env
|
||||
cache
|
||||
.DS_Store
|
||||
log.json
|
@ -3,7 +3,7 @@ authors = ["monoid"]
|
||||
language = "ko"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Software Requirement Specification"
|
||||
title = "Software Development Comprehensive Document"
|
||||
|
||||
[preprocessor]
|
||||
[preprocessor.mermaid]
|
||||
|
3434
cache/issues.json
vendored
Normal file
3434
cache/issues.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,10 @@
|
||||
# Summary
|
||||
|
||||
[Index](./index.md)
|
||||
|
||||
- [Introduction](./intro.md)
|
||||
- [Overall Description](./overall.md)
|
||||
- [Specific Requirement](./specific.md)
|
||||
- [Supporting information](./support.md)
|
||||
- [Requirement Specification Supporting Information](./support.md)
|
||||
- [Architecture](./architecture.md)
|
||||
- [Testing](./testing.md)
|
@ -1,4 +1,4 @@
|
||||
# 5. 설계
|
||||
# 5. 설계(Architecture)
|
||||
|
||||
## 5.1 UML
|
||||
|
||||
@ -398,15 +398,15 @@ FsManager ..> "1" RPCMessageManager
|
||||
FsManager ..> "1" FsGetResult
|
||||
```
|
||||
|
||||
## 5.2 의사코드
|
||||
## 5.2 의사코드(Pseudo Code)
|
||||
|
||||
의사코드는 다음과 같이 진행된다.
|
||||
|
||||
### 서버 RPC 메세지 처리
|
||||
### 5.2.1 서버 RPC 메세지 처리
|
||||
|
||||
클라이언트는 RPC를 진행하기 위해 웹소켓을 연결합니다. 웹소켓의 주소 `/ws`에
|
||||
도달하기 위해서 먼저 라우팅이 진행이 됩니다. 라우팅은 `TreeRouter` 클래스에서
|
||||
진행됩니다.
|
||||
클라이언트는 RPC를 진행하기 위해 웹소켓을 연결한다. 웹소켓의 주소 `/ws`에
|
||||
도달하기 위해서 먼저 라우팅이 진행이 된다. 라우팅은 `TreeRouter` 클래스에서
|
||||
진행된다.
|
||||
```
|
||||
Input: req Request
|
||||
Output: Response
|
||||
@ -419,7 +419,8 @@ fn Route(req){
|
||||
response with 404 Not Found
|
||||
}
|
||||
```
|
||||
마침내 엔드포인트에 도달하게 되면 웹소켓을 얻어내고 새로운 연결을 등록합니다.
|
||||
마침내 엔드포인트에 도달하게 되면 웹소켓을 얻어내고 새로운 연결을 등록한다.
|
||||
여기서 reqeust session으로 유저를 얻어내서 접속하는 것에 대해 주의하라.
|
||||
```
|
||||
Input: req Request
|
||||
Output: Response
|
||||
@ -431,8 +432,8 @@ fn RPCHandleEndpoint(req){
|
||||
res
|
||||
}
|
||||
```
|
||||
이제부터 메세지를 받을 수 있습니다.
|
||||
메세지가 오면 이 함수가 실행이 됩니다.
|
||||
이제부터 `RPCMethod` 메세지를 받을 수 있다.
|
||||
메세지가 오면 함수 `handleMessage` 가 실행이 호출된다.
|
||||
```
|
||||
Input: message on send
|
||||
Output: response
|
||||
@ -443,11 +444,11 @@ fn Connection.handleMessage(msg: string){
|
||||
}
|
||||
```
|
||||
|
||||
디스패치가 성공적으로 이루어지면 RPC 작업를 처리하는 함수에 도착합니다.
|
||||
청크 작업을 예로 들겠습니다. 청크 작업에서는 권한을 확인하고
|
||||
명령의 충돌을 히스토리를 비교하며 다시 적용하며 해결합니다.
|
||||
디스패치가 성공적으로 이루어지면 RPC 작업를 처리하는 함수에 도착한다.
|
||||
청크 작업을 예로 들면 이렇다. 청크 작업에서는 권한을 확인하고
|
||||
명령의 충돌을 히스토리를 비교하며 다시 적용하며 해결한다.
|
||||
그리고 요구된 작업을 처리하고 문서의 `updatedAt`을 업데이트하고
|
||||
문서 업데이트 사실을 이 문서를 보고 있던 참여자에게 전파합니다.
|
||||
문서 업데이트 사실을 이 문서를 보고 있던 참여자에게 전파한다.
|
||||
```
|
||||
Input: conn Connection
|
||||
Input: m RPCChunkMethod
|
||||
@ -481,15 +482,15 @@ fn ChunkOperation(conn, m){
|
||||
}
|
||||
```
|
||||
|
||||
문서 작업도 마찬가지로 이루어집니다.
|
||||
문서 작업도 마찬가지로 이루어진다.
|
||||
|
||||
### 클라이언트의 메세지 처리 동기화
|
||||
### 5.2.2 클라이언트의 메세지 처리 동기화
|
||||
|
||||
클라이언트에서는 다음과 같은 일이 일어납니다.
|
||||
클라이언트에서는 다음과 같은 일이 일어난다.
|
||||
|
||||
먼저 `notification`을 받습니다. 그러면 모든 `DocumentViewModel`에게 이벤트를 전달합니다.
|
||||
먼저 `notification`을 받는다. 그러면 모든 `DocumentViewModel`에게 이벤트를 전달한다.
|
||||
그리고 각각의 `DocumentViewModel`은 자기 문서에 일어난 일인지 확인하고 `ChunkListMutator`를 만들어서
|
||||
문서에 적용합니다.
|
||||
문서에 적용한다.
|
||||
```
|
||||
Input: e RPCNotification
|
||||
Input: this document view model
|
||||
@ -502,7 +503,7 @@ fn updateOnNotification(this,notification){
|
||||
}
|
||||
```
|
||||
|
||||
문서의 레디큐에 mutator를 집어넣고 `seq` 번호가 기다리는 것이면 실행하고 업데이트합니다.
|
||||
문서의 레디큐에 mutator를 집어넣고 `seq` 번호가 기다리는 것이면 실행하고 업데이트한다.
|
||||
|
||||
```
|
||||
Input: mutator ChunkListMutator
|
||||
@ -531,7 +532,7 @@ fn apply(this, mutator, updatedAt, seq){
|
||||
}
|
||||
```
|
||||
|
||||
### 다른 작업들
|
||||
### 5.2.3 다른 작업들
|
||||
|
||||
```
|
||||
module chunk {
|
||||
@ -550,7 +551,8 @@ module chunk {
|
||||
}
|
||||
}
|
||||
|
||||
chunkViewer(chunk : Chunk, focusedChunk : State<string>, deleteThis : () => void) : Component {
|
||||
chunkViewer(chunk : Chunk, focusedChunk : State<string>,
|
||||
deleteThis : () => void) : Component {
|
||||
var mode = Read
|
||||
|
||||
var c = new Component(
|
||||
@ -665,7 +667,7 @@ module settings {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
module frontend {
|
||||
main() : Component {
|
||||
|
52
src/index.md
Normal file
52
src/index.md
Normal file
@ -0,0 +1,52 @@
|
||||
# 목차(Index)
|
||||
|
||||
1. [소개(Introduction)](./intro.md)
|
||||
1. [목적(Purpose)](./intro.md#11-목적purpose)
|
||||
2. [범위(scope)](./intro.md#12-범위scope)
|
||||
3. [용어 및 약어 정의(Definitions, acronyms and abbreviations)](./intro.md#13-용어-및-약어-정의definitions-acronyms-and-abbreviations)
|
||||
4. [참고자료(References)](./intro.md#14-참고자료references)
|
||||
5. [개요(Overview)](./intro.md#15-개요overview)
|
||||
2. [전체 시스템 개요(Overall description)](./overall.md)
|
||||
1. [제품 관점(Product perspective)](./overall.md#21-제품-관점product-perspective)
|
||||
1. [시스템 인터페이스(System interfaces)](./overall.md#211-시스템-인터페이스system-interfaces)
|
||||
2. [사용자 인터페이스(User interfaces)](./overall.md#212-사용자-인터페이스user-interfaces)
|
||||
3. [하드웨어 인터페이스(Hardware interfaces)](./overall.md#213-하드웨어-인터페이스hardware-interfaces)
|
||||
4. [소프트웨어 인터페이스(Software interfaces)](./overall.md#214-소프트웨어-인터페이스software-interfaces)
|
||||
5. [통신 인터페이스(Communications interfaces)](./overall.md#215-통신-인터페이스communications-interfaces)
|
||||
6. [메모리 제약사항(Memory constraints)](./overall.md#216-메모리-제약사항memory-constraints)
|
||||
7. [운영(Operations)](./overall.md#217-운영operations)
|
||||
8. [사이트 적용 요건(Site adaption requirements)](./overall.md#218-사이트-적용-요건site-adaption-requirements)
|
||||
2. [제품 기능(Product functions)](./overall.md#22-제품-기능product-functions)
|
||||
<%
|
||||
const table = it.table;
|
||||
let index = 1;
|
||||
for (const [c,issues] of table) {
|
||||
const name = `${c} Operation`;
|
||||
const href = `2.2.${index} ${c} Operation`
|
||||
%><%= `${index++}. [${name}](./overall.md#${it.toHeadId(href)})\n ` %><%}%><%="\n"%>
|
||||
3. [상세요구사항(Specific Requirements)](./specific.md)
|
||||
1. [외부 인터페이스 요구사항(External interface requirements)](./specific.md#31-외부-인터페이스-요구사항external-interface-requirements)
|
||||
2. [기능 요구사항(Functional requirements)](./specific.md#32-기능-요구사항functional-requirements)
|
||||
<%= it.issues.map((i)=>`(#${i.number}) ${i.title}`).map(
|
||||
x=>`* [${x}](./specific.md#${it.toHeadId(x)})`
|
||||
).join("\n ") %><%= "\n"%>
|
||||
3. [성능 요구사항(Performance requirements)](./specific.md#33-성능-요구사항performance-requirements)
|
||||
4. [논리적 데이터베이스 요구사항(Logical database requirements)](./specific.md#34-논리적-데이터베이스-요구사항logical-database-requirements)
|
||||
5. [설계 제약사항(Design constraints)](./specific.md#35-설계-제약사항design-constraints)
|
||||
1. [표준 준수(Standards compliance)](./specific.md#351-표준-준수standards-compliance)
|
||||
6. [소프트웨어 시스템 속성(Software system attributes)](./specific.md#36-소프트웨어-시스템-속성software-system-attributes)
|
||||
7. [상세 요구사항의 구성(Organizing the specific requirements)](./specific.md#37-상세-요구사항의-구성organizing-the-specific-requirements)
|
||||
1. [객체(Objects)](./specific.md#371-객체objects)
|
||||
2. [사용자 인터페이스 상세](./specific.md#372-사용자-인터페이스-상세)
|
||||
4. [요구사항 명세 추가 이력 (Requirement Specification Supporting Information)](./support.md)
|
||||
1. [부록(Appendixes)](./support.md#41-부록appendixes)
|
||||
2. [개발 환경(Development Environment)](./support.md#42-개발-환경development-environment)
|
||||
3. [일정표(Schedule)](./support.md#43-일정표schedule)
|
||||
5. [설계(Architecture)](./architecture.md)
|
||||
1. [UML](./architecture.md#51-uml)
|
||||
1. [Server Side UML](./architecture.md#511-server-side-uml)
|
||||
2. [Client Side UML](./architecture.md#512-client-side-uml)
|
||||
2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code)
|
||||
6. [시험(Testing)](./testing.md)
|
||||
1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test)
|
||||
2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test)
|
23
src/intro.md
23
src/intro.md
@ -1,16 +1,21 @@
|
||||
# 1. 소개(Introduction)
|
||||
|
||||
> Version : 1.0.1
|
||||
|
||||
본 문서는 전북대학교 컴퓨터공학과의 Floor 팀에서 Scrap Yard라는 어플리케이션을 설계 및 구현하기 위한 소프트웨어 요구사항 명세서(SRS)이다.
|
||||
> Version : 1.1.0
|
||||
>
|
||||
> Version Hash : <%= it.gitHash %>
|
||||
>
|
||||
> 본 문서는 전북대학교 컴퓨터공학과의 Floor 팀에서 Scrap Yard라는 어플리케이션을
|
||||
> 설계 및 구현하기 위한 소프트웨어 요구사항 명세서(SRS)이자 어플리케이션의 구조를
|
||||
> 나타내는 설계서이자 어플리케이션의 시험 결과 보고서이다. 정리하면 어플리케이션을
|
||||
> 개발하면서 발생하는 산출물들을 정리한 문서이다.
|
||||
|
||||
## 1.1. 목적(Purpose)
|
||||
|
||||
본 문서의 목적은 프로젝트의 관련된 모든 아이디어들을 정리하고 분석해서 나열하는 것이다. 또한 프로젝트를 더 잘 이해하기 위해 이 제품이 어떻게 사용될지 예측하고 분류하고, 나중에 개발될 요소를 설명하고, 고려 중이지만 폐기될 수 있는 요구사항들을 문서화한다.
|
||||
본 문서의 목적은 첫째로 프로젝트의 관련된 모든 아이디어들을 정리하고 분석해서 나열하는 것이다. 또한 프로젝트를 더 잘 이해하기 위해 이 제품이 어떻게 사용될지 예측하고 분류하고, 나중에 개발될 요소를 설명하고, 고려 중이지만 폐기될 수 있는 요구사항들을 문서화한다. 둘째로 이러한 요구사항을 해결하기 위해 만들어진 설계를 나열하고 문서화하는 것이다. 셋째로 이러한 설계로 구현된 프로그램을 시험을 하고 그 결과를 정리해서 보기좋게 문서화하고 색인하는 것이다.
|
||||
|
||||
## 1.2. 범위(Scope)
|
||||
|
||||
본 문서의 범위는 ScrapYard의 기능들과 그 환경이다.
|
||||
본 문서의 범위는 ScrapYard의 기능들과 그 환경 그리고 상세 설계 및 인터페이스이다.
|
||||
|
||||
ScrapYard는 문서 작성 밎 문서를 아카이빙 할 수 있는 웹 어플리케이션이다. 같이 제공되는 확장기능을 통해 북마크(즐겨찾기)를 구조적으로 보관할 수 있고 미리보기를 보여줄 수 있다.
|
||||
|
||||
@ -27,7 +32,8 @@ ScrapYard는 문서 작성 밎 문서를 아카이빙 할 수 있는 웹 어플
|
||||
|
||||
## 1.4. 참고자료(References)
|
||||
|
||||
- [repo](https://github.com/vi117/scrap-yard)
|
||||
- [repository](https://github.com/vi117/scrap-yard)
|
||||
- [document repository](https://git.prelude.duckdns.org/ScrapYard/SRS)
|
||||
- [react](https://reactjs.org/)
|
||||
- [recoil](https://recoiljs.org/)
|
||||
- [MUI](https://mui.com/)
|
||||
@ -36,4 +42,7 @@ ScrapYard는 문서 작성 밎 문서를 아카이빙 할 수 있는 웹 어플
|
||||
|
||||
## 1.5. 개요(Overview)
|
||||
|
||||
2장에서는 종합적인 요구사항을 서술하고, 3장에서는 기능 및 UI에 대해서 상세한 요구사항을 설명한다.
|
||||
2장과 3장은 SRS(소프트웨어 요구사항 명세서)의 양식에 맞추어 작성되었다. 2장에서는 종합적인 요구사항을 서술하고, 3장에서는 기능 및 UI에 대해서 상세한 요구사항을 설명한다.
|
||||
4장은 SRS의 추가 이력사항에 대해서 서술한다. 여기에서는 어플리케이션 개발 일정표가 포함되어 있다.
|
||||
5장은 어플리케이션의 상세한 설계에 대해서 서술한다.
|
||||
6장은 개발된 어플리케이션의 시험과 그 결과에 대해서 서술한다.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 2. 전체 시스템 개요(Overall description)
|
||||
|
||||
### 2.1. 제품 관점(Product perspective)
|
||||
## 2.1. 제품 관점(Product perspective)
|
||||
|
||||
### 2.1.1. 시스템 인터페이스(System interfaces)
|
||||
|
||||
@ -45,17 +45,7 @@
|
||||
본 프로젝트의 결과물은 다음과 같은 기능을 수행한다.
|
||||
|
||||
<%
|
||||
const table = new Map();
|
||||
it.issues.forEach((x)=>{
|
||||
const category = x.title.split(":")[0];
|
||||
if(!category) return;
|
||||
let c = table.get(category)
|
||||
if(!c){
|
||||
c = [];
|
||||
table.set(category,c);
|
||||
}
|
||||
c.push(x);
|
||||
})
|
||||
const table = it.table;
|
||||
let index = 1;
|
||||
for (const [c,issues] of table) {
|
||||
%><%= `### 2.2.${index++} ${c} Operation\n\n` %><%
|
||||
@ -64,7 +54,6 @@
|
||||
%><%=`${subIndex++}. #${i.number} ${i.title}\n` %><%
|
||||
}
|
||||
%>
|
||||
|
||||
<%
|
||||
}
|
||||
%>
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 추가 이력 (Supporting Information)
|
||||
# 요구사항 명세 추가 이력 (Requirement Specification Supporting Information)
|
||||
|
||||
## 4.1. 부록(Appendixes)
|
||||
|
||||
|
451
src/testing.md
451
src/testing.md
@ -1,335 +1,130 @@
|
||||
# Testing
|
||||
# 6. 시험(Testing)
|
||||
|
||||
## 유닛 테스트
|
||||
## 6.1 유닛 테스트(Unit test)
|
||||
|
||||
유닛 테스트로 69.6%의 Line Coverage와 73.4%의 Function Coverage를 달성했다.
|
||||
다음과 같은 로그가 있다.
|
||||
유닛 테스트로 63.7%의 Line Coverage와 67.5%의 Function Coverage를 달성했다.
|
||||
### permission.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|permission.test|✅|14ms|
|
||||
|permission empty|✅|16ms|
|
||||
### session.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|Session : set |✅|14ms|
|
||||
|Session : delete |✅|16ms|
|
||||
|Login Handler : login with invalid format |✅|15ms|
|
||||
|Login Handler : login with invalid password |✅|16ms|
|
||||
|Login Handler : login |✅|15ms|
|
||||
|Login Handler : logout with no session |✅|17ms|
|
||||
|Login Handler : logout |✅|16ms|
|
||||
|getSession|✅|15ms|
|
||||
|getSession with invalid cookie|✅|15ms|
|
||||
### user.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|user.createAdminUser|✅|5ms|
|
||||
### filedoc.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|readDocFile|✅|12ms|
|
||||
|readDocFile: not found|✅|15ms|
|
||||
|readDocFile: invalid json|✅|15ms|
|
||||
|saveDocFile|✅|15ms|
|
||||
### methodHandle.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|methodHandle: basic methods|✅|14ms|
|
||||
|methodHandle: not found|✅|15ms|
|
||||
|methodHandle: options|✅|17ms|
|
||||
### route.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|route: basic route|✅|6ms|
|
||||
|route: double slash route|✅|12ms|
|
||||
|route: double match|✅|16ms|
|
||||
|route: test context|✅|15ms|
|
||||
|route: test regex|✅|15ms|
|
||||
|route: test not found|✅|2ms|
|
||||
|route: encode_route|✅|12ms|
|
||||
|route: router in router|✅|15ms|
|
||||
### chunk.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|basic chunk operation : create chunk |✅|13ms|
|
||||
|basic chunk operation : delete chunk |✅|15ms|
|
||||
|basic chunk operation : modify chunk |✅|16ms|
|
||||
|basic chunk operation : move chunk |✅|16ms|
|
||||
|basic chunk operation : invalid chunk operation |✅|16ms|
|
||||
|test chunk notification operation|✅|16ms|
|
||||
|test chunk conflict|✅|15ms|
|
||||
|test chunk conflict resolve with history|✅|32ms|
|
||||
### doc.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|handleDocumentMethod|✅|5ms|
|
||||
|handleTagMethod : setTag |✅|9ms|
|
||||
|handleTagMethod : getTag |✅|16ms|
|
||||
|handleTagMethod : conflict |✅|16ms|
|
||||
### share.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|handleShareGetInfo|✅|16ms|
|
||||
|handleShareDocMethod|✅|2ms|
|
||||
|handleShareMethod with no existing share token|✅|12ms|
|
||||
### server.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|server rpc test|✅|4s|
|
||||
### fswatcher.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|WatchFilteredReadWriter|✅|79ms|
|
||||
### readWriter.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|QueueReadWriter|✅|57ms|
|
||||
### util.test.ts
|
||||
| name | result | duration |
|
||||
| ---- | ------ | -------- |
|
||||
|watcher util isHidden|✅|5ms|
|
||||
|
||||
```
|
||||
running 2 tests from ./src/auth/permission.test.ts
|
||||
permission.test ... ok (8ms)
|
||||
permission empty ... ok (16ms)
|
||||
running 4 tests from ./src/auth/session.test.ts
|
||||
Session ...
|
||||
set ... ok (9ms)
|
||||
delete ... ok (16ms)
|
||||
ok (42ms)
|
||||
Login Handler ...
|
||||
login with invalid format ... ok (15ms)
|
||||
login with invalid password ... ok (16ms)
|
||||
login ... ok (16ms)
|
||||
logout with no session ... ok (16ms)
|
||||
logout ... ok (16ms)
|
||||
ok (96ms)
|
||||
getSession ... ok (16ms)
|
||||
getSession with invalid cookie ... ok (16ms)
|
||||
running 1 test from ./src/auth/user.test.ts
|
||||
user.createAdminUser ... ok (15ms)
|
||||
running 4 tests from ./src/document/filedoc.test.ts
|
||||
readDocFile ... ok (19ms)
|
||||
readDocFile: not found ... ok (16ms)
|
||||
readDocFile: invalid json ... ok (16ms)
|
||||
saveDocFile ... ok (15ms)
|
||||
running 3 tests from ./src/router/methodHandle.test.ts
|
||||
methodHandle: basic methods ... ok (8ms)
|
||||
methodHandle: not found ... ok (16ms)
|
||||
methodHandle: options ... ok (16ms)
|
||||
running 8 tests from ./src/router/route.test.ts
|
||||
route: basic route ... ok (10ms)
|
||||
route: double slash route ... ok (16ms)
|
||||
route: double match ... ok (16ms)
|
||||
route: test context ... ok (16ms)
|
||||
route: test regex ... ok (16ms)
|
||||
route: test not found ... ok (16ms)
|
||||
route: encode_route ... ok (2ms)
|
||||
route: router in router ... ok (13ms)
|
||||
running 4 tests from ./src/rpc/chunk.test.ts
|
||||
basic chunk operation ...
|
||||
create chunk ... ok (19ms)
|
||||
delete chunk ... ok (15ms)
|
||||
modify chunk ... ok (15ms)
|
||||
move chunk ... ok (15ms)
|
||||
invalid chunk operation ... ok (17ms)
|
||||
ok (98ms)
|
||||
test chunk notification operation ... ok (15ms)
|
||||
test chunk conflict ... ok (16ms)
|
||||
test chunk conflict resolve with history ... ok (32ms)
|
||||
running 2 tests from ./src/rpc/doc.test.ts
|
||||
handleDocumentMethod ... ok (4ms)
|
||||
handleTagMethod ...
|
||||
setTag ... ok (13ms)
|
||||
getTag ... ok (15ms)
|
||||
conflict ... ok (15ms)
|
||||
ok (61ms)
|
||||
running 3 tests from ./src/rpc/share.test.ts
|
||||
handleShareGetInfo ... ok (18ms)
|
||||
handleShareDocMethod ... ok (15ms)
|
||||
handleShareMethod with no existing share token ... ok (16ms)
|
||||
running 1 test from ./src/server.test.ts
|
||||
server rpc test ... ok (1s)
|
||||
running 3 tests from ./src/setting.test.ts
|
||||
setting: basic ... ok (35ms)
|
||||
setting: default value ... ok (7ms)
|
||||
setting: defered register ... ok (16ms)
|
||||
test result: ok. 35 passed (15 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (2s)
|
||||
```
|
||||
### Total
|
||||
|
||||
| name | passed | Steps | Failed | duration |
|
||||
| ---- | ------ | ----- | ------ | -------- |
|
||||
| ok | 35 | 15 | 0 | 4726ms |
|
||||
|
||||
## 기능 테스트
|
||||
## 6.2 기능 테스트(Functional Test)
|
||||
|
||||
### Chunk
|
||||
<% const keys = Array.from(it.table.keys()); %>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Focus/Unfocus</td>
|
||||
<td>1. 청크를 클릭한다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>remove</td>
|
||||
<td>1. 청크를 삭제하는 버튼을 클릭한다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-1</td>
|
||||
<td>render - markdown</td>
|
||||
<td>1. 마크다운 청크 렌더링을 확인한다.</td>
|
||||
<td> # 제목 </td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-2</td>
|
||||
<td>render - latex</td>
|
||||
<td>1. LaTex 청크 렌더링을 확인한다.</td>
|
||||
<td> sum^n_{n=0}n = \frac{n(n+1)}2$$ </td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-3</td>
|
||||
<td>render - link</td>
|
||||
<td>1. Image 청크 렌더링을 확인한다.</td>
|
||||
<td>http://picsum.photos</td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>previews</td>
|
||||
<td>1. Katex 청크의 미리보기를 본다.</td>
|
||||
<td> sum^n_{n=0}n = \frac{n(n+1)}2$$ </td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>autocomplete</td>
|
||||
<td>1. <kbd>Ctrl+Space</kbd>를 눌러 자동완성을 시도한다.</td>
|
||||
<td></td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>swap positions</td>
|
||||
<td>1. 청크의 위치를 바꾼다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>27-1</td>
|
||||
<td>edit</td>
|
||||
<td>1. 청크를 수정한다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>27-2</td>
|
||||
<td>edit chunk conflict</td>
|
||||
<td>1. 청크를 수정모드에 들어간다.</td>
|
||||
<td></td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Document
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>view Chunk</td>
|
||||
<td>1. 문서를 열어 청크가 렌더링되는지 본다.</td>
|
||||
<td>test.syd</td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>add/delete tag</td>
|
||||
<td>1. 문서에 태그를 추가한다.<br>2. 문서에 태그를 삭제한다.</td>
|
||||
<td>A</td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Drag And Drop Upload,</td>
|
||||
<td>1. 텍스트를 드래그한다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### File
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>create/delete/rename file</td>
|
||||
<td>1. 파일을 만든다.</td>
|
||||
<td>test.txt</td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>upload/download files</td>
|
||||
<td>1. 파일을 업로드한다.</td>
|
||||
<td>test.txt</td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>export document</td>
|
||||
<td>1. export 버튼을 누른다.</td>
|
||||
<td></td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Search
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Document Search</td>
|
||||
<td>1. 검색버튼을 눌러 검색을 한다.</td>
|
||||
<td>chunk</td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Stash
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>render</td>
|
||||
<td>1. 스태시가 그려지는지 확인한다</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>add</td>
|
||||
<td>1. 청크를 추가한다</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>remove</td>
|
||||
<td>1. 청크를 삭제한다</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>21</td>
|
||||
<td>Drag and Drop to Document</td>
|
||||
<td>1. 청크로부터 문서로 청크를 옮긴다.</td>
|
||||
<td></td>
|
||||
<td>P</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Management
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Content</th>
|
||||
<th>Procedure</th>
|
||||
<th>Test Data</th>
|
||||
<th>P/F</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>22</td>
|
||||
<td>Login</td>
|
||||
<td>1. 비밀번호를 입력한다.</td>
|
||||
<td>admin</td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>24</td>
|
||||
<td>Localization</td>
|
||||
<td>1. 다른언어를 지원하는지 언어를 바꿔 확인한다</td>
|
||||
<td></td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<% keys.forEach(c=>{
|
||||
const issues = it.table.get(c);
|
||||
const ts = issues.map(x=> it.testcases.filter(y=>y.id==x.number)).flat();
|
||||
if(ts?.length == 0) return; %>
|
||||
<%~`\n\n### ${c}\n\n`%>
|
||||
<%~"<table>"%>
|
||||
<%~"<thead>"%>
|
||||
<%~"<tr>"%>
|
||||
<%~"<th>ID</th>"%>
|
||||
<%~"<th>Content</th>"%>
|
||||
<%~"<th>Procedure</th>"%>
|
||||
<%~"<th>Test Data</th>"%>
|
||||
<%~"<th>P/F</th>"%>
|
||||
<%~"</tr>"%>
|
||||
<%~"</thead>"%>
|
||||
<%~"<tbody>"%>
|
||||
<% ts.forEach((y,i)=>{
|
||||
const id = y.subId ? `${y.id}-${y.subId}` : y.id;%>
|
||||
<%~"<tr>"%>
|
||||
<%~`<td>${id}</td>`%>
|
||||
<%~`<td>${y.content}</td>`%>
|
||||
<%~`<td>${y.procedure}</td>`%>
|
||||
<%~`<td>${y.testData ?? ""}</td>`%>
|
||||
<%~`<td>${y.pass ? "✅" : "❌"}</td>`%>
|
||||
<%~"</tr>"%>
|
||||
<%});%>
|
||||
<%~"</tbody>"%>
|
||||
<%~"</table>"%>
|
||||
<% });%>
|
@ -14,9 +14,14 @@ import "https://deno.land/std@0.136.0/dotenv/load.ts";
|
||||
* console.log(issues);
|
||||
* ```
|
||||
*/
|
||||
export async function getIssues(repo: string, token: string): Promise<Issue[]> {
|
||||
export async function getIssues(repo: string, token?: string): Promise<Issue[]> {
|
||||
//check https://docs.github.com/en/rest/reference/issues#list-repository-issues
|
||||
const res = await fetch(`https://api.github.com/repos/${repo}/issues?per_page=100&labels=feature&state=all`, {
|
||||
const url = `https://api.github.com/repos/${repo}/issues?per_page=100`;
|
||||
if(!token) {
|
||||
const res = await fetch(url);
|
||||
return await res.json();
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Token ${token}`
|
||||
@ -28,15 +33,7 @@ export async function getIssues(repo: string, token: string): Promise<Issue[]> {
|
||||
|
||||
if (import.meta.main) {
|
||||
const args = parse(Deno.args);
|
||||
const token = args.token ?? Deno.env.get("GITHUB_TOKEN");
|
||||
if(typeof token !== "string"){
|
||||
console.error("invalid type: token must be string");
|
||||
Deno.exit(1);
|
||||
}
|
||||
if(!token) {
|
||||
console.error("GITHUB_TOKEN is not set");
|
||||
Deno.exit(1);
|
||||
}
|
||||
let token = args.token ?? Deno.env.get("GITHUB_TOKEN");
|
||||
const issues = await getIssues("vi117/scrap-yard", token);
|
||||
issues.sort((a, b) => a.number - b.number);
|
||||
const content = JSON.stringify(issues, null, 2);
|
||||
|
@ -4,6 +4,20 @@ import { WriterHandler } from "https://deno.land/std@0.143.0/log/handlers.ts";
|
||||
import * as Eta from "https://deno.land/x/eta@v1.12.3/mod.ts";
|
||||
import { Issue } from "./githubType.ts";
|
||||
|
||||
import testcaseData from "./testcases.json" assert { type: "json"};
|
||||
|
||||
type Testcase = {
|
||||
id: number,
|
||||
subId: number | null,
|
||||
content: string,
|
||||
procedure: string,
|
||||
testData: string| null,
|
||||
expected: string,
|
||||
actual: string,
|
||||
pass: boolean
|
||||
}
|
||||
const testcases = testcaseData as Testcase[];
|
||||
|
||||
class StderrHandler extends WriterHandler {
|
||||
protected _writer: Deno.Writer;
|
||||
#encoder: TextEncoder;
|
||||
@ -19,18 +33,20 @@ class StderrHandler extends WriterHandler {
|
||||
}
|
||||
|
||||
interface Book {
|
||||
sections: Section[];
|
||||
sections: BookItem[];
|
||||
}
|
||||
interface Section {
|
||||
//chapter or separtor or PartTitle
|
||||
type Separator = "Separator";
|
||||
//type PartTitle = ;
|
||||
type BookItem = Separator | {
|
||||
Chapter: Chapter;
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
name: string;
|
||||
content: string;
|
||||
/** section number */
|
||||
number?: number[];
|
||||
sub_items: Section[];
|
||||
sub_items: BookItem[];
|
||||
path?: string;
|
||||
source_path?: string;
|
||||
parent_names: string[];
|
||||
@ -62,6 +78,17 @@ async function getIssues(){
|
||||
return issues;
|
||||
}
|
||||
|
||||
function toHeadId(name: string){
|
||||
return name.replaceAll(/[^A-Za-z\s0-9]/gi,"").toLocaleLowerCase().replaceAll(" ","-");
|
||||
}
|
||||
|
||||
async function getCurrentGitHash() {
|
||||
const res = await Deno.run({cmd:["git", "rev-parse", "HEAD"], stdout:"piped"});
|
||||
const hash = new TextDecoder().decode(await res.output()).trim();
|
||||
res.close();
|
||||
return hash;;
|
||||
}
|
||||
|
||||
async function main(args: string[]) {
|
||||
if (args.length > 1) {
|
||||
//log.info(`args: ${JSON.stringify(args)}`);
|
||||
@ -70,16 +97,37 @@ async function main(args: string[]) {
|
||||
}
|
||||
}
|
||||
const issues = await getIssues();
|
||||
|
||||
log.info(`start`);
|
||||
const table = new Map();
|
||||
issues.forEach((x)=>{
|
||||
const category = x.title.split(":")[0];
|
||||
if(!category) return;
|
||||
let c = table.get(category)
|
||||
if(!c){
|
||||
c = [];
|
||||
table.set(category,c);
|
||||
}
|
||||
c.push(x);
|
||||
});
|
||||
const gitHash = await getCurrentGitHash();
|
||||
|
||||
log.info(`start\n`);
|
||||
const data = await readAll(Deno.stdin);
|
||||
const jsonText = new TextDecoder().decode(data);
|
||||
await Deno.writeTextFile("log.json", jsonText);
|
||||
const [context, book] = JSON.parse(jsonText) as [any, Book];
|
||||
book.sections.forEach(x=>{
|
||||
x.Chapter.content = Eta.render(x.Chapter.content, {
|
||||
issues: issues
|
||||
}) as string;
|
||||
if(x === "Separator"){
|
||||
//skip
|
||||
}
|
||||
else {
|
||||
x.Chapter.content = Eta.render(x.Chapter.content, {
|
||||
issues: issues,
|
||||
table: table,
|
||||
gitHash: gitHash,
|
||||
toHeadId: toHeadId,
|
||||
testcases: testcases
|
||||
}) as string;
|
||||
}
|
||||
})
|
||||
//Deno.stderr.writeSync(new TextEncoder().encode(`context: ${JSON.stringify(context)}\n`));
|
||||
console.log(JSON.stringify(book));
|
||||
|
@ -1,3 +1,5 @@
|
||||
import testcaseData from "./testcases.json" assert { type: "json"};
|
||||
|
||||
type Testcase = {
|
||||
id: number,
|
||||
subId: number | null,
|
||||
@ -9,261 +11,11 @@ type Testcase = {
|
||||
pass: boolean
|
||||
}
|
||||
|
||||
const testcase: Testcase[] = [
|
||||
{
|
||||
"id": 1,
|
||||
"subId": null,
|
||||
"content": "Focus/Unfocus",
|
||||
"procedure": "1. 청크를 클릭한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"subId": null,
|
||||
"content": "remove",
|
||||
"procedure": "1. 청크를 삭제하는 버튼을 클릭한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 1,
|
||||
"content": "render - markdown",
|
||||
"procedure": "1. 마크다운 청크 렌더링을 확인한다.",
|
||||
"testData": " # 제목 ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 2,
|
||||
"content": "render - latex",
|
||||
"procedure": "1. LaTex 청크 렌더링을 확인한다.",
|
||||
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 3,
|
||||
"content": "render - link",
|
||||
"procedure": "1. Image 청크 렌더링을 확인한다.",
|
||||
"testData": " http://picsum.photos/200/300 ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"subId": null,
|
||||
"content": "previews",
|
||||
"procedure": "1. Katex 청크의 미리보기를 본다.",
|
||||
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"subId": null,
|
||||
"content": "autocomplete",
|
||||
"procedure": "1. 자동완성을 시험한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"subId": null,
|
||||
"content": "swap positions",
|
||||
"procedure": "1. 청크의 위치를 바꾼다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"subId": 1,
|
||||
"content": "edit",
|
||||
"procedure": "1. 청크를 수정한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"subId": 2,
|
||||
"content": "edit chunk conflict",
|
||||
"procedure": "1. 청크를 수정모드에 들어간다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"subId": null,
|
||||
"content": "view Chunk",
|
||||
"procedure": "1. 문서를 열어 청크가 렌더링되는지 본다.",
|
||||
"testData": "test.syd",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"subId": null,
|
||||
"content": "add/delete tag",
|
||||
"procedure": "1. 문서에 태그를 추가한다.<br>2. 문서에 태그를 삭제한다.",
|
||||
"testData": "A",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"subId": null,
|
||||
"content": "Drag And Drop Upload,",
|
||||
"procedure": "1. 텍스트를 드래그한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"subId": null,
|
||||
"content": "create/delete/rename file",
|
||||
"procedure": "1. 파일을 만든다.",
|
||||
"testData": "test.txt",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"subId": null,
|
||||
"content": "upload/download files",
|
||||
"procedure": "1. 파일을 업로드한다.",
|
||||
"testData": "test.txt",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"subId": null,
|
||||
"content": "export document",
|
||||
"procedure": "1. export 버튼을 누른다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"subId": null,
|
||||
"content": "render",
|
||||
"procedure": "1. 스태시가 그려지는지 확인한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"subId": null,
|
||||
"content": "add",
|
||||
"procedure": "1. 청크를 추가한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"subId": null,
|
||||
"content": "remove",
|
||||
"procedure": "1. 청크를 삭제한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"subId": null,
|
||||
"content": "Drag and Drop to Document",
|
||||
"procedure": "1. 청크로부터 문서로 청크를 옮긴다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"subId": null,
|
||||
"content": "Login",
|
||||
"procedure": "1. 비밀번호를 입력한다.",
|
||||
"testData": "admin",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"subId": null,
|
||||
"content": "Localization",
|
||||
"procedure": "1. 다른언어를 지원하는지 언어를 바꿔 확인한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"subId": null,
|
||||
"content": "Document Search",
|
||||
"procedure": "1. 검색해본다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 85,
|
||||
"subId": null,
|
||||
"content": "Permission",
|
||||
"procedure": "1. 각각의 기능들에 권한없이 시도한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
}
|
||||
]
|
||||
const testcase: Testcase[] = testcaseData;
|
||||
|
||||
import {Issue} from "./githubType.ts"
|
||||
|
||||
async function readContent(path?: string): Promise<string> {
|
||||
let content = "[]";
|
||||
if (path) {
|
||||
content = await Deno.readTextFile(path);
|
||||
}
|
||||
else throw new Error("No input provided. path or stdin.");
|
||||
return content;
|
||||
}
|
||||
|
||||
const data = await readContent("../build/issues.json")
|
||||
const data = await Deno.readTextFile("../cache/issues.json")
|
||||
|
||||
const issues = JSON.parse(data) as Issue[]
|
||||
const table = new Map<string, Issue[]>();
|
||||
@ -276,7 +28,7 @@ const table = new Map<string, Issue[]>();
|
||||
table.set(category,c);
|
||||
}
|
||||
c.push(x);
|
||||
})
|
||||
});
|
||||
|
||||
const keys = Array.from(table.keys());
|
||||
|
||||
|
252
tools/testcases.json
Normal file
252
tools/testcases.json
Normal file
@ -0,0 +1,252 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"subId": null,
|
||||
"content": "Focus/Unfocus",
|
||||
"procedure": "1. 청크를 클릭한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"subId": null,
|
||||
"content": "remove",
|
||||
"procedure": "1. 청크를 삭제하는 버튼을 클릭한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 1,
|
||||
"content": "render - markdown",
|
||||
"procedure": "1. 마크다운 청크 렌더링을 확인한다.",
|
||||
"testData": " # 제목 ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 2,
|
||||
"content": "render - latex",
|
||||
"procedure": "1. LaTex 청크 렌더링을 확인한다.",
|
||||
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subId": 3,
|
||||
"content": "render - link",
|
||||
"procedure": "1. Image 청크 렌더링을 확인한다.",
|
||||
"testData": " http://picsum.photos/200/300 ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"subId": null,
|
||||
"content": "previews",
|
||||
"procedure": "1. Katex 청크의 미리보기를 본다.",
|
||||
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"subId": null,
|
||||
"content": "autocomplete",
|
||||
"procedure": "1. 자동완성을 시험한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"subId": null,
|
||||
"content": "swap positions",
|
||||
"procedure": "1. 청크의 위치를 바꾼다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"subId": 1,
|
||||
"content": "edit",
|
||||
"procedure": "1. 청크를 수정한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"subId": 2,
|
||||
"content": "edit chunk conflict",
|
||||
"procedure": "1. 청크를 수정모드에 들어간다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"subId": null,
|
||||
"content": "view Chunk",
|
||||
"procedure": "1. 문서를 열어 청크가 렌더링되는지 본다.",
|
||||
"testData": "test.syd",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"subId": null,
|
||||
"content": "add/delete tag",
|
||||
"procedure": "1. 문서에 태그를 추가한다.<br>2. 문서에 태그를 삭제한다.",
|
||||
"testData": "A",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"subId": null,
|
||||
"content": "Drag And Drop Upload,",
|
||||
"procedure": "1. 텍스트를 드래그한다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"subId": null,
|
||||
"content": "create/delete/rename file",
|
||||
"procedure": "1. 파일을 만든다.",
|
||||
"testData": "test.txt",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"subId": null,
|
||||
"content": "upload/download files",
|
||||
"procedure": "1. 파일을 업로드한다.",
|
||||
"testData": "test.txt",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"subId": null,
|
||||
"content": "export document",
|
||||
"procedure": "1. export 버튼을 누른다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"subId": null,
|
||||
"content": "render",
|
||||
"procedure": "1. 스태시가 그려지는지 확인한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"subId": null,
|
||||
"content": "add",
|
||||
"procedure": "1. 청크를 추가한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"subId": null,
|
||||
"content": "remove",
|
||||
"procedure": "1. 청크를 삭제한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"subId": null,
|
||||
"content": "Drag and Drop to Document",
|
||||
"procedure": "1. 청크로부터 문서로 청크를 옮긴다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"subId": null,
|
||||
"content": "Login",
|
||||
"procedure": "1. 비밀번호를 입력한다.",
|
||||
"testData": "admin",
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"subId": null,
|
||||
"content": "Localization",
|
||||
"procedure": "1. 다른언어를 지원하는지 언어를 바꿔 확인한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"subId": null,
|
||||
"content": "Theme",
|
||||
"procedure": "1. Theme를 바꾸어서 제대로 변경되는지 시험해본다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"subId": null,
|
||||
"content": "Document Search",
|
||||
"procedure": "1. 검색해본다.",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": false
|
||||
},
|
||||
{
|
||||
"id": 85,
|
||||
"subId": null,
|
||||
"content": "Permission",
|
||||
"procedure": "1. 각각의 기능들에 권한없이 시도한다",
|
||||
"testData": null,
|
||||
"expected": "",
|
||||
"actual": "",
|
||||
"pass": true
|
||||
}
|
||||
]
|
206
tools/unittestToDoc.ts
Normal file
206
tools/unittestToDoc.ts
Normal file
@ -0,0 +1,206 @@
|
||||
const test_stdout: string = `running 2 tests from ./src/auth/permission.test.ts
|
||||
permission.test ... ok (14ms)
|
||||
permission empty ... ok (16ms)
|
||||
running 4 tests from ./src/auth/session.test.ts
|
||||
Session ...
|
||||
set ... ok (14ms)
|
||||
delete ... ok (16ms)
|
||||
ok (49ms)
|
||||
Login Handler ...
|
||||
login with invalid format ... ok (15ms)
|
||||
login with invalid password ... ok (16ms)
|
||||
login ... ok (15ms)
|
||||
logout with no session ... ok (17ms)
|
||||
logout ... ok (16ms)
|
||||
ok (94ms)
|
||||
getSession ... ok (15ms)
|
||||
getSession with invalid cookie ... ok (15ms)
|
||||
running 1 test from ./src/auth/user.test.ts
|
||||
user.createAdminUser ... ok (5ms)
|
||||
running 4 tests from ./src/document/filedoc.test.ts
|
||||
readDocFile ... ok (12ms)
|
||||
readDocFile: not found ... ok (15ms)
|
||||
readDocFile: invalid json ... ok (15ms)
|
||||
saveDocFile ... ok (15ms)
|
||||
running 3 tests from ./src/router/methodHandle.test.ts
|
||||
methodHandle: basic methods ... ok (14ms)
|
||||
methodHandle: not found ... ok (15ms)
|
||||
methodHandle: options ... ok (17ms)
|
||||
running 8 tests from ./src/router/route.test.ts
|
||||
route: basic route ... ok (6ms)
|
||||
route: double slash route ... ok (12ms)
|
||||
route: double match ... ok (16ms)
|
||||
route: test context ... ok (15ms)
|
||||
route: test regex ... ok (15ms)
|
||||
route: test not found ... ok (2ms)
|
||||
route: encode_route ... ok (12ms)
|
||||
route: router in router ... ok (15ms)
|
||||
running 4 tests from ./src/rpc/chunk.test.ts
|
||||
basic chunk operation ...
|
||||
create chunk ... ok (13ms)
|
||||
delete chunk ... ok (15ms)
|
||||
modify chunk ... ok (16ms)
|
||||
move chunk ... ok (16ms)
|
||||
invalid chunk operation ... ok (16ms)
|
||||
ok (96ms)
|
||||
test chunk notification operation ... ok (16ms)
|
||||
test chunk conflict ... ok (15ms)
|
||||
test chunk conflict resolve with history ... ok (32ms)
|
||||
running 2 tests from ./src/rpc/doc.test.ts
|
||||
handleDocumentMethod ... ok (5ms)
|
||||
handleTagMethod ...
|
||||
setTag ... ok (9ms)
|
||||
getTag ... ok (16ms)
|
||||
conflict ... ok (16ms)
|
||||
ok (58ms)
|
||||
running 3 tests from ./src/rpc/share.test.ts
|
||||
handleShareGetInfo ... ok (16ms)
|
||||
handleShareDocMethod ... ok (2ms)
|
||||
handleShareMethod with no existing share token ... ok (12ms)
|
||||
running 1 test from ./src/server.test.ts
|
||||
server rpc test ... ok (4s)
|
||||
running 1 test from ./src/watcher/fswatcher.test.ts
|
||||
WatchFilteredReadWriter ... ok (79ms)
|
||||
running 1 test from ./src/watcher/readWriter.test.ts
|
||||
QueueReadWriter ... ok (57ms)
|
||||
running 1 test from ./src/watcher/util.test.ts
|
||||
watcher util isHidden ... ok (5ms)
|
||||
|
||||
test result: ok. 35 passed (15 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (6s)
|
||||
`;
|
||||
|
||||
interface TestStep{
|
||||
name: string;
|
||||
result: boolean;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface TestResult{
|
||||
name: string;
|
||||
result: boolean;
|
||||
time: number;
|
||||
steps: TestStep[];
|
||||
}
|
||||
|
||||
interface TestFiles{
|
||||
path: string;
|
||||
tests: TestResult[];
|
||||
}
|
||||
|
||||
const lines = test_stdout.split("\n");
|
||||
let cur = 0;
|
||||
|
||||
function getMs(time: string){
|
||||
if (time.endsWith("ms")){
|
||||
return parseInt(time.replace("ms", ""));
|
||||
}
|
||||
return parseInt(time.replace("s", "")) * 1000;
|
||||
}
|
||||
|
||||
let testFiles: TestFiles[] = [];
|
||||
|
||||
function parseTest(): TestResult {
|
||||
const m = /^(.*) \.\.\.(.*)/.exec(lines[cur]);
|
||||
let steps: TestStep[] = []
|
||||
if(m){
|
||||
const name = m[1];
|
||||
const status = m[2];
|
||||
let result = status.includes("ok");
|
||||
let time = 0;
|
||||
if(status === ""){
|
||||
cur++;
|
||||
while(!lines[cur].startsWith("ok")){
|
||||
const s = /^ (.*) \.\.\. (.*) \((\d+m?s)\)/.exec(lines[cur]);
|
||||
if(!s){
|
||||
console.log("unexpected line: " + lines[cur]);
|
||||
Deno.exit(1);
|
||||
}
|
||||
const stepname = s[1];
|
||||
const stepstatus = s[2];
|
||||
const steptime = s[3];
|
||||
//console.log(`${name} ${stepname} ${stepstatus} ${steptime}`);
|
||||
steps.push({name: stepname, result: stepstatus.includes("ok"), time: getMs(steptime)});
|
||||
cur++;
|
||||
}
|
||||
result = lines[cur].startsWith("ok");
|
||||
time = getMs(lines[cur].match(/\d+m?s/)![0]);
|
||||
}
|
||||
else{
|
||||
time = getMs(status.match(/\d+m?s/)![0]);
|
||||
}
|
||||
//console.log(name, result, time);
|
||||
cur++;
|
||||
return {name, result, time, steps};
|
||||
}
|
||||
console.log("unexpected line: " + lines[cur]);
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
while(cur < lines.length){
|
||||
const line = lines[cur];
|
||||
if(line === ""){
|
||||
cur++;
|
||||
break;
|
||||
}
|
||||
const m = /^running (\d+) tests? from (.+)/.exec(lines[cur]);
|
||||
if(m){
|
||||
const num = parseInt(m[1]);
|
||||
const file = m[2];
|
||||
//console.log(`${num} tests from ${file}`);
|
||||
cur++;
|
||||
let tests = [];
|
||||
for(let i = 0; i < num; i++){
|
||||
const test = parseTest();
|
||||
tests.push(test);
|
||||
}
|
||||
testFiles.push({path: file, tests});
|
||||
}
|
||||
else {
|
||||
console.log("%c unexpected : "+line,"color: red");
|
||||
break;
|
||||
}
|
||||
//console.log("endwith " + cur);
|
||||
}
|
||||
|
||||
const lastResultRegex = /test result: (.*)\. (\d+) passed \((\d+) steps\); (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out \((\d+m?s)\)/;
|
||||
const mm = lastResultRegex.exec(lines[cur]);
|
||||
if(!mm){
|
||||
console.log("unexpected line: " + lines[cur]);
|
||||
Deno.exit(1);
|
||||
}
|
||||
const mok = mm[1];
|
||||
const numPassed = parseInt(mm[2]);
|
||||
const numSteps = parseInt(mm[3]);
|
||||
const numFailed = parseInt(mm[4]);
|
||||
const numIgnored = parseInt(mm[5]);
|
||||
const numMeasured = parseInt(mm[6]);
|
||||
const numFiltered = parseInt(mm[7]);
|
||||
const time = getMs(mm[8]);
|
||||
|
||||
|
||||
import * as path from "https://deno.land/std@0.142.0/path/mod.ts";
|
||||
|
||||
let duration = 0;
|
||||
//console.log(testFiles);
|
||||
testFiles.forEach(file => {
|
||||
console.log(`### ${path.basename(file.path)}`);
|
||||
console.log(`| name | result | duration |`);
|
||||
console.log(`| ---- | ------ | -------- |`);
|
||||
file.tests.forEach(test => {
|
||||
if(test.steps.length > 0){
|
||||
test.steps.forEach(step => {
|
||||
console.log(`|${test.name + " : " + step.name} |${step.result ? "✅" : "❌"}|${step.time}ms|`);
|
||||
duration += step.time;
|
||||
});
|
||||
}
|
||||
else{
|
||||
console.log(`|${test.name}|${test.result ? "✅" : "❌"}|${test.time}ms|`);
|
||||
duration += test.time;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`### Total`);
|
||||
console.log(`| name | passed | Steps | Failed | duration |`);
|
||||
console.log(`| ---- | ------ | ----- | ------ | -------- |`);
|
||||
console.log(`| ${mok} | ${numPassed} | ${numSteps} | ${numFailed} | ${duration}ms |`);
|
Loading…
Reference in New Issue
Block a user