diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fd6777..8ba36b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,6 @@ name: Test + on: push: branches: [main] @@ -10,16 +11,16 @@ jobs: vendor: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 with: - go-version: 1.14.x + go-version: 1.20.x - name: get dependencies run: go get -v -t -d ./... - name: vendoring run: go mod vendor - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: repository path: . @@ -28,10 +29,10 @@ jobs: runs-on: ubuntu-latest needs: vendor steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 # https://github.com/golangci/golangci-lint-action#how-to-use - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: version: latest @@ -39,10 +40,10 @@ jobs: runs-on: ubuntu-latest needs: vendor steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v4 with: - go-version: 1.14.x - - uses: actions/download-artifact@v2 + go-version: 1.20.x + - uses: actions/download-artifact@v3 with: name: repository path: . @@ -53,10 +54,10 @@ jobs: runs-on: ubuntu-latest needs: vendor steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v4 with: - go-version: 1.14.x - - uses: actions/download-artifact@v2 + go-version: 1.20.x + - uses: actions/download-artifact@v3 with: name: repository path: . diff --git a/.goreleaser.yml b/.goreleaser.yml index 2fa156a..1dc1e89 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -14,13 +14,6 @@ builds: - linux - windows - darwin -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..612026f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +golang 1.20.3 +goreleaser 1.18.2 diff --git a/.whitesource b/.whitesource index 31d7220..71c41fb 100644 --- a/.whitesource +++ b/.whitesource @@ -1,3 +1,3 @@ { - "settingsInheritedFrom": "SmartBear/whitesource-config@main" + "settingsInheritedFrom": "oselvar/whitesource-config@main" } \ No newline at end of file diff --git a/AsaduzzamanICSM2013LineDraft.pdf b/AsaduzzamanICSM2013LineDraft.pdf new file mode 100644 index 0000000..46fc5cd Binary files /dev/null and b/AsaduzzamanICSM2013LineDraft.pdf differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f17d3..faaa73d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.3] - 2022-02-03 ### Changed -- Change module name from `github.com/aslakhellesoy/lhdiff` to `github.com/SmartBear/lhdiff` +- Change module name from `github.com/aslakhellesoy/lhdiff` to `github.com/oselvar/lhdiff` ## [0.0.2] - 2022-02-03 ### Added @@ -43,12 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First functional version -[Unreleased]: https://github.com/SmartBear/lhdiff/compare/v0.1.2...HEAD -[0.1.2]: https://github.com/SmartBear/lhdiff/compare/v0.1.1...v0.1.2 -[0.1.1]: https://github.com/SmartBear/lhdiff/compare/v0.1.0...v0.1.1 -[0.1.0]: https://github.com/SmartBear/lhdiff/compare/v0.0.5...v0.1.0 -[0.0.5]: https://github.com/SmartBear/lhdiff/compare/v0.0.4...v0.0.5 -[0.0.4]: https://github.com/SmartBear/lhdiff/compare/v0.0.3...v0.0.4 -[0.0.3]: https://github.com/SmartBear/lhdiff/compare/v0.0.2...v0.0.3 -[0.0.2]: https://github.com/SmartBear/lhdiff/compare/v0.0.1...v0.0.2 -[0.0.1]: https://github.com/SmartBear/lhdiff/compare/6084d5de2ec3dbb25767433e79ab840d5941c2de...v0.0.1 +[Unreleased]: https://github.com/oselvar/lhdiff/compare/v0.1.2...HEAD +[0.1.2]: https://github.com/oselvar/lhdiff/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/oselvar/lhdiff/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/oselvar/lhdiff/compare/v0.0.5...v0.1.0 +[0.0.5]: https://github.com/oselvar/lhdiff/compare/v0.0.4...v0.0.5 +[0.0.4]: https://github.com/oselvar/lhdiff/compare/v0.0.3...v0.0.4 +[0.0.3]: https://github.com/oselvar/lhdiff/compare/v0.0.2...v0.0.3 +[0.0.2]: https://github.com/oselvar/lhdiff/compare/v0.0.1...v0.0.2 +[0.0.1]: https://github.com/oselvar/lhdiff/compare/6084d5de2ec3dbb25767433e79ab840d5941c2de...v0.0.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e1a585..3bc52ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,4 +2,4 @@ ## Build - goreleaser build --single-target --snapshot --rm-dist + goreleaser build --single-target --snapshot --clean diff --git a/README.md b/README.md index 506b25f..a636cb4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Test](https://github.com/SmartBear/lhdiff/actions/workflows/test.yml/badge.svg)](https://github.com/SmartBear/lhdiff/actions/workflows/test.yml) +[![Test](https://github.com/oselvar/lhdiff/actions/workflows/test.yml/badge.svg)](https://github.com/oselvar/lhdiff/actions/workflows/test.yml) # lhdiff A Lightweight Hybrid Approach for Tracking Source Lines. @@ -8,7 +8,7 @@ Unix diff, and works independently of the file contents (programming language). ## Install - go get github.com/SmartBear/lhdiff + go get github.com/oselvar/lhdiff To install from source, see [CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/cmd/lhdiff/main.go b/cmd/lhdiff/main.go index 82be7a9..eefb2a0 100644 --- a/cmd/lhdiff/main.go +++ b/cmd/lhdiff/main.go @@ -3,9 +3,9 @@ package main import ( "flag" "fmt" - "github.com/SmartBear/lhdiff" - "io/ioutil" "os" + + "github.com/oselvar/lhdiff" ) func main() { @@ -13,8 +13,8 @@ func main() { flag.Parse() leftFile := flag.Arg(0) rightFile := flag.Arg(1) - left, _ := ioutil.ReadFile(leftFile) - right, _ := ioutil.ReadFile(rightFile) + left, _ := os.ReadFile(leftFile) + right, _ := os.ReadFile(rightFile) mappings, err := lhdiff.Lhdiff(string(left), string(right), 4, !*compact) if err != nil { diff --git a/go.mod b/go.mod index 36dd2e2..cd6b612 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ -module github.com/SmartBear/lhdiff +module github.com/oselvar/lhdiff -go 1.17 +go 1.20 require ( - github.com/ianbruene/go-difflib v1.2.0 github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba + github.com/mongodb-forks/go-difflib v1.3.1 github.com/rexsimiloluwah/distance_metrics v0.0.0-20211020112549-67979eee6077 - github.com/sourcegraph/go-diff v0.6.1 + github.com/sourcegraph/go-diff v0.7.0 ) diff --git a/go.sum b/go.sum index c0d04c6..008847c 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,14 @@ github.com/dgryski/trifles v0.0.0-20200830180326-aaf60a07f6a3 h1:JibukGTEjdN4VMX github.com/dgryski/trifles v0.0.0-20200830180326-aaf60a07f6a3/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/ianbruene/go-difflib v1.2.0 h1:iARmgaCq6nW5QptdoFm0PYAyNGix3xw/xRgEwphJSZw= -github.com/ianbruene/go-difflib v1.2.0/go.mod h1:uJbrQ06VPxjRiRIrync+E6VcWFGW2dWqw2gvQp6HQPY= github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba h1:keZ4vJpYOVm6yrjLzZ6QgozbEBaT0GjfH30ihbO67+4= github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba/go.mod h1:kaXTPU4xitQT0rfT7/i9O9Gm8acSh3DXr0p4y3vKqiE= +github.com/mongodb-forks/go-difflib v1.3.1 h1:e+DVrR/0m+1i3rAhqHcXlJAsyNl71w3RGtUgYAmCctU= +github.com/mongodb-forks/go-difflib v1.3.1/go.mod h1:HQwBVyCQe8Qoqa5oARs7axPr/BNmWpXPS0ozENUCaS0= github.com/rexsimiloluwah/distance_metrics v0.0.0-20211020112549-67979eee6077 h1:UARAHYmaBmaZFFgO/3gdyMaw6ZJw7sGM2vF5NWUsDNM= github.com/rexsimiloluwah/distance_metrics v0.0.0-20211020112549-67979eee6077/go.mod h1:c9cZ1im6joocUOHKTdfD5H8iLrG6yMFyzQQ0iVv/nog= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ= -github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/lhdiff.go b/lhdiff.go index 85c9f41..ddfd457 100644 --- a/lhdiff.go +++ b/lhdiff.go @@ -3,14 +3,15 @@ package lhdiff import ( "bytes" "fmt" - "github.com/ianbruene/go-difflib/difflib" - levenshtein "github.com/ka-weihe/fast-levenshtein" - "github.com/sourcegraph/go-diff/diff" "math" "regexp" "sort" "strconv" "strings" + + levenshtein "github.com/ka-weihe/fast-levenshtein" + "github.com/mongodb-forks/go-difflib/difflib" + "github.com/sourcegraph/go-diff/diff" ) type LineInfo struct { @@ -55,7 +56,23 @@ const ContextSimilarityFactor = 0.4 const ContentSimilarityFactor = 0.6 const SimilarityThreshold = 0.45 -func Lhdiff(left string, right string, contextSize int, includeIdenticalLines bool) ([][]int, error) { +/** + * Returns a list of mappings between the lines of the left and right file. + * Each mapping is a pair of line numbers, where -1 indicates that the line is not present in the file. + * The mappings are sorted by the line number of the left file. + * If includeIdenticalLines is true, then lines that are identical in both files are included in the mappings. + * Otherwise, only lines that are not identical are included. + * The contextSize parameter determines how many lines of context are used to determine the similarity of lines. + * The context lines are not included in the mappings. + * The context lines are lines that are not blank and do not consist of only curly braces or parenthesis. + * The context lines are used to determine the similarity of lines. + * The similarity of lines is determined by a combination of the normalized Levenshtein distance of the content of the lines and the cosine similarity of the context of the lines. + * The similarity of lines is only considered if it is above a certain threshold. + * The mappings are determined by first finding the unchanged lines using the difflib library. + * Then, for each line in the right file, the most similar line in the left file is found. + * The most similar line is the line with the highest combined similarity. + */ +func Lhdiff(left string, right string, contextSize int, includeIdenticalLines bool) ([][]uint32, error) { leftLines := ConvertToLinesWithoutNewLine(left) rightLines := ConvertToLinesWithoutNewLine(right) @@ -88,6 +105,11 @@ func Lhdiff(left string, right string, contextSize int, includeIdenticalLines bo leftLineInfos := MakeLineInfos(leftLineNumbers, leftLines, contextSize) rightLineInfos := MakeLineInfos(rightLineNumbers, rightLines, contextSize) + // TODO: We have combinatorial explosion here.... + // See section D in the paper about simhash + // We need to compute that here. + // See HDiffSHMatching.match - line 287-293 + // Maybe do this in parallel? for _, rightLineInfo := range rightLineInfos { var similarPairCandidates []LinePair for _, leftLineInfo := range leftLineInfos { @@ -124,25 +146,26 @@ func Lhdiff(left string, right string, contextSize int, includeIdenticalLines bo rightLineNumbers = append(rightLineNumbers, rightLineNumber) } } - return lineMappings(allPairs, len(leftLines), rightLineNumbers, includeIdenticalLines), nil + return makeLineMappings(allPairs, len(leftLines), rightLineNumbers, includeIdenticalLines), nil } -func lineMappings(linePairs map[int]LinePair, leftLineCount int, newRightLines []int, includeIdenticalLines bool) [][]int { - lines := make([][]int, 0) +func makeLineMappings(linePairs map[int]LinePair, leftLineCount int, newRightLines []int, includeIdenticalLines bool) [][]uint32 { + fmt.Println("linePairs:", len(linePairs)) + lineMappings := make([][]uint32, 0) for leftLineNumber := 0; leftLineNumber < leftLineCount; leftLineNumber++ { pair, exists := linePairs[leftLineNumber] if !exists { - lines = append(lines, []int{leftLineNumber, -1}) + lineMappings = append(lineMappings, []uint32{uint32(leftLineNumber + 1), 0}) } else { if includeIdenticalLines || !(pair.left.content == pair.right.content && leftLineNumber == pair.right.lineNumber) { - lines = append(lines, []int{leftLineNumber, pair.right.lineNumber}) + lineMappings = append(lineMappings, []uint32{uint32(leftLineNumber + 1), uint32(pair.right.lineNumber + 1)}) } } } for _, rightLine := range newRightLines { - lines = append(lines, []int{-1, rightLine}) + lineMappings = append(lineMappings, []uint32{0, uint32(rightLine + 1)}) } - return lines + return lineMappings } func MakeLineInfos(lineNumbers []int, lines []string, contextSize int) []*LineInfo { @@ -299,7 +322,7 @@ func RemoveMultipleSpaceAndTrim(s string) string { return strings.TrimSpace(re.ReplaceAllString(s, " ")) + "\n" } -func PrintMappings(mappings [][]int) error { +func PrintMappings(mappings [][]uint32) error { for _, mapping := range mappings { _, err := fmt.Printf("%s,%s\n", toString(mapping[0]), toString(mapping[1])) if err != nil { @@ -309,12 +332,12 @@ func PrintMappings(mappings [][]int) error { return nil } -func toString(i int) string { +func toString(i uint32) string { var left string - if i == -1 { + if i == 0 { left = "_" } else { - left = strconv.Itoa(i + 1) + left = strconv.FormatUint(uint64(i), 10) } return left } diff --git a/lhdiff_test.go b/lhdiff_test.go index ae44fd1..688f600 100644 --- a/lhdiff_test.go +++ b/lhdiff_test.go @@ -323,7 +323,7 @@ func ExampleLhdiff_withDataFromMainGo() { import ( "flag" - "github.com/SmartBear/lhdiff" + "github.com/oselvar/lhdiff" "io/ioutil" ) @@ -341,7 +341,7 @@ func main() { import ( "flag" - "github.com/SmartBear/lhdiff" + "github.com/oselvar/lhdiff" "io/ioutil" ) @@ -387,7 +387,7 @@ func ExampleLhdiff_withDataFromLhdiffGo() { import ( "bytes" "fmt" - "github.com/ianbruene/go-difflib/difflib" + "github.com/mongodb-forks/go-difflib/difflib" t "github.com/rexsimiloluwah/distance_metrics/text" "github.com/sourcegraph/go-diff/diff" "math" @@ -677,7 +677,7 @@ func RemoveMultipleSpaceAndTrim(s string) string { import ( "bytes" "fmt" - "github.com/ianbruene/go-difflib/difflib" + "github.com/mongodb-forks/go-difflib/difflib" t "github.com/rexsimiloluwah/distance_metrics/text" "github.com/sourcegraph/go-diff/diff" "math" @@ -940,7 +940,7 @@ func RemoveMultipleSpaceAndTrim(s string) string { return strings.TrimSpace(re.ReplaceAllString(s, " ")) + "\n" } ` - // https://github.com/SmartBear/lhdiff/commit/4ae3495de0c31675940861592a3929df8154785f + // https://github.com/oselvar/lhdiff/commit/4ae3495de0c31675940861592a3929df8154785f mappings, err := Lhdiff(left, right, 4, true) printErr(err) err = PrintMappings(mappings) @@ -1061,8 +1061,8 @@ func RemoveMultipleSpaceAndTrim(s string) string { //112,_ //113,_ //114,_ - //115,_ - //116,134 + //115,134 + //116,_ //117,_ //118,_ //119,_ @@ -1260,6 +1260,38 @@ func RemoveMultipleSpaceAndTrim(s string) string { //_,242 } +func ExampleLhdiff_withDataFromTestDataTypeScript() { + left, err := os.ReadFile("testdata/typescript-1800-loc/new") + printErr(err) + right, err := os.ReadFile("testdata/typescript-1800-loc/old") + printErr(err) + + mappings, err := Lhdiff(string(left), string(right), 4, false) + printErr(err) + err = PrintMappings(mappings) + printErr(err) + + // Output: + //1,1 + //2,2 + //3,3 + //4,4 + //5,5 + //6,6 + //7,7 + //8,8 + //9,9 + //10,11 + //11,12 + //12,_ + //13,13 + //14,14 + //15,15 + //16,16 + //17,17 + //_,10 +} + func printErr(err error) { if err != nil { _, _ = fmt.Fprintln(os.Stderr, err.Error()) diff --git a/testdata/typescript-1800-loc/new b/testdata/typescript-1800-loc/new new file mode 100644 index 0000000..e98da32 --- /dev/null +++ b/testdata/typescript-1800-loc/new @@ -0,0 +1,1839 @@ +import type { OpenAPIObject } from 'openapi3-ts/oas31'; + +export const openapi: OpenAPIObject = { + "openapi": "3.1.0", + "info": { + "title": "Test Bank API", + "version": "0.1.0", + "description": "The Test Bank API provides the following functionality:\n\n* Store changesets (training data)\n* Store verdicts (training data)\n* Request a TCP" + }, + "servers": [ + { + "url": "http://localhost:3001" + } + ], + "tags": [ + { + "name": "Auth", + "description": "Auth related endpoints" + }, + { + "name": "Projects", + "description": "Project related endpoints" + }, + { + "name": "Repos", + "description": "Repo related endpoints" + }, + { + "name": "Versions", + "description": "Version related endpoints" + }, + { + "name": "Changesets", + "description": "Changeset related endpoints" + }, + { + "name": "Verdicts", + "description": "Verdict related endpoints" + }, + { + "name": "TCP", + "description": "TCP related endpoints" + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "Login": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "github" + ] + }, + "redirectTo": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "provider", + "redirectTo" + ], + "description": "Create a Login URL." + }, + "ProjectInsert": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The display name of the project." + } + }, + "required": [ + "name" + ], + "description": "The properties of a project to create." + }, + "RepoInsert": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The display name of the repo." + } + }, + "required": [ + "name" + ], + "description": "The properties of a repo to create." + }, + "VersionRow": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Version" + }, + "name": { + "type": "string", + "description": "The display name of the version." + } + }, + "required": [ + "id", + "name" + ], + "description": "The created version." + }, + "VersionInsert": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The display name of the version." + } + }, + "required": [ + "name" + ], + "description": "The properties of a version to create." + }, + "RepoTest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to the test file, relative to the root of the git repo." + }, + "line": { + "type": "integer", + "description": "The line number where the test is defined. This is 1-indexed." + }, + "name": { + "type": "string", + "description": "The name of the test. This is purely informational and will be shown in the UI." + } + }, + "required": [ + "path", + "line", + "name" + ], + "description": "A single test case originating from a file in a git repo." + }, + "RepoTestTcpOutput": { + "type": "object", + "properties": { + "orderedRepoTests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepoTest" + } + } + }, + "required": [ + "orderedRepoTests" + ] + }, + "TestLine": { + "type": "object", + "properties": { + "line": { + "type": "integer", + "description": "The line number where the test is defined. This is 1-indexed." + }, + "name": { + "type": "string", + "description": "The name of the test. This is purely informational and will be shown in the UI." + } + }, + "required": [ + "line", + "name" + ], + "description": "A test line describes a single test, defined on a specific line in a file." + }, + "TestFile": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "testLines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TestLine" + } + } + }, + "required": [ + "path", + "testLines" + ], + "description": "A test file is a file containing one or more tests." + }, + "TestRepoTcpInput": { + "type": "object", + "properties": { + "testFiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TestFile" + } + } + }, + "required": [ + "testFiles" + ], + "description": "TCP input for repos where automated tests and SUT are stored in different repos." + }, + "ApfdMetric": { + "type": "object", + "properties": { + "apfd": { + "type": "number" + }, + "apfdData": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "apfd", + "apfdData" + ] + }, + "ApfdMetrics": { + "type": "object", + "properties": { + "identity": { + "$ref": "#/components/schemas/ApfdMetric" + }, + "optimal": { + "$ref": "#/components/schemas/ApfdMetric" + }, + "worst": { + "$ref": "#/components/schemas/ApfdMetric" + }, + "random": { + "$ref": "#/components/schemas/ApfdMetric" + } + }, + "required": [ + "identity", + "optimal", + "worst", + "random" + ] + }, + "VerdictsOutput": { + "type": "object", + "properties": { + "metrics": { + "$ref": "#/components/schemas/ApfdMetrics" + } + }, + "required": [ + "metrics" + ] + }, + "ExternalTest": { + "type": "object", + "properties": { + "externalId": { + "type": "string", + "description": "The external ID of the test. This must be specified if the test is not an automated test under source control. The ID must be unique within the project." + }, + "name": { + "type": "string", + "description": "The name of the test. This is purely informational and will be shown in the UI." + } + }, + "required": [ + "externalId", + "name" + ], + "description": "A single test case originating from outside of a git repo." + }, + "ExternalVerdict": { + "type": "object", + "properties": { + "externalTest": { + "$ref": "#/components/schemas/ExternalTest" + }, + "status": { + "type": "string", + "enum": [ + "passed", + "failed", + "unknown" + ], + "description": "The status of the test." + } + }, + "required": [ + "externalTest", + "status" + ], + "description": "The verdict of an external test, i.e. whether it passed or failed." + }, + "ChangesetOutput": { + "type": "object", + "properties": { + "repoId": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "changesetId": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Changeset" + } + }, + "required": [ + "repoId", + "changesetId" + ] + }, + "LineChange": { + "type": "object", + "properties": { + "oldLine": { + "type": "integer", + "description": "The line number before the change. 0 means the line did not exist before the change (it has been added)." + }, + "newLine": { + "type": "integer", + "description": "The line number after the change. 0 means the line did not exist after the change (it has been removed)." + }, + "cyclomate": { + "type": "boolean", + "description": "Whether the new line adds cyclomatic complexity. For example, if it is an if/for statement." + } + }, + "required": [ + "oldLine", + "newLine" + ], + "description": "A line change describes a change to a line between two git revisions. Line numbers are 1-indexed." + }, + "FileChange": { + "type": "object", + "properties": { + "oldPath": { + "type": "string", + "description": "The path to the file before the change. The path is relative to the root of the git repo." + }, + "newPath": { + "type": "string", + "description": "The path to the file after the change. The path is relative to the root of the git repo." + }, + "lineChanges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineChange" + }, + "description": "The changed lines." + } + }, + "required": [ + "oldPath", + "newPath", + "lineChanges" + ], + "description": "A file change describes a change to a file between two git revisions." + }, + "Changeset": { + "type": "object", + "properties": { + "parentSha": { + "type": "string", + "description": "Git commit SHA" + }, + "sha": { + "type": "string", + "description": "Git commit SHA" + }, + "fileChanges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileChange" + } + }, + "loc": { + "type": "integer" + } + }, + "required": [ + "parentSha", + "sha", + "fileChanges", + "loc" + ], + "description": "A changeset describes the changes between two git revisions." + }, + "RepoVerdict": { + "type": "object", + "properties": { + "repoTest": { + "$ref": "#/components/schemas/RepoTest" + }, + "status": { + "type": "string", + "enum": [ + "passed", + "failed", + "unknown" + ], + "description": "The status of the test." + } + }, + "required": [ + "repoTest", + "status" + ], + "description": "The verdict of a repo test, i.e. whether it passed or failed." + }, + "MonorepoTcpInput": { + "type": "object", + "properties": { + "changeset": { + "$ref": "#/components/schemas/Changeset" + }, + "testFiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TestFile" + } + } + }, + "required": [ + "changeset", + "testFiles" + ], + "description": "TCP input for monorepos where automated tests and code for SUT are stored in a single repo." + } + }, + "parameters": {} + }, + "paths": { + "/api/v1/login": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Get a login URL", + "description": "Log in with OAuth", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Login" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Login" + } + } + } + }, + "responses": { + "302": { + "description": "The login URL" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects": { + "post": { + "tags": [ + "Projects" + ], + "summary": "Create a new Project", + "description": "Create a new Project.", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectInsert" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/ProjectInsert" + } + } + } + }, + "responses": { + "200": { + "description": "The new Project", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "name": { + "type": "string" + }, + "apiToken": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "apiToken" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects/{projectId}": { + "get": { + "tags": [ + "Projects" + ], + "summary": "Get information about a project", + "description": "Retrieves a project that has been previously created.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "projectId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Project information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects/{projectId}/repos": { + "post": { + "tags": [ + "Repos" + ], + "summary": "Create a new Repo", + "description": "Create a new Repo underneath a project.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "projectId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepoInsert" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RepoInsert" + } + } + } + }, + "responses": { + "200": { + "description": "The new Repo", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects/{projectId}/versions": { + "post": { + "tags": [ + "Versions" + ], + "summary": "Create a new Version", + "description": "Create a new Version underneath a project.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "projectId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionInsert" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/VersionInsert" + } + } + } + }, + "responses": { + "200": { + "description": "The new Version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionRow" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects/{projectId}/versions/{versionId}/tcp": { + "post": { + "tags": [ + "TCP" + ], + "summary": "Create a Test Repo TCP", + "description": "Use *Test Repo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in one or more *other* Git repositories", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "projectId", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Version" + }, + "required": true, + "description": "The ID of the Version", + "name": "versionId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestRepoTcpInput" + } + } + } + }, + "responses": { + "200": { + "description": "Tests ordered by TCP score. The tests most likely to fail come first.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepoTestTcpOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/projects/{projectId}/versions/{versionId}/verdicts": { + "post": { + "tags": [ + "Verdicts" + ], + "summary": "Store external verdicts", + "description": "Store verdicts for external tests. \n\nThis should be used when tests are external to the repo (e.g. manual tests), \nor in a multi-repo setting (automated tests in a separate repo from the SUT).", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "projectId", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Version" + }, + "required": true, + "description": "The ID of the Version", + "name": "versionId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalVerdict" + }, + "description": "The verdicts in the order they were executed. If they were executed in parallel, the order should be the devised order by a TCP." + } + } + } + }, + "responses": { + "200": { + "description": "The result of storing the verdicts", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerdictsOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/repos/{repoId}/changesets": { + "post": { + "tags": [ + "Changesets" + ], + "summary": "Store a new changeset.", + "description": "A changeset represents the changes between two revisions of a git repository.\nOptionally specify a versionId query parameter to attach it to a version.\n", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "repoId", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Version" + }, + "required": false, + "description": "The ID of the Version", + "name": "versionId", + "in": "query" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Changeset" + } + } + } + }, + "responses": { + "200": { + "description": "The created changeset", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "409": { + "description": "Conflict. The changeset already exists.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/repos/{repoId}/changesets/{changesetId}/verdicts": { + "post": { + "tags": [ + "Verdicts" + ], + "summary": "Store repo verdicts", + "description": "Store verdicts for tests stored in a Git repo.", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "repoId", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Changeset" + }, + "required": true, + "description": "The ID of the Changeset", + "name": "changesetId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepoVerdict" + }, + "description": "The verdicts in the order they were executed. If they were executed in parallel, the order should be the devised order by a TCP." + } + } + } + }, + "responses": { + "200": { + "description": "The result of storing the verdicts", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerdictsOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/repos/{repoId}/tcp": { + "post": { + "tags": [ + "TCP" + ], + "summary": "Create a Monorepo TCP", + "description": "Use *Monorepo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in the *same* Git repositories", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "repoId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonorepoTcpInput" + } + } + } + }, + "responses": { + "200": { + "description": "Tests ordered by TCP score. The tests most likely to fail come first.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepoTestTcpOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + }, + "/api/v1/repos/{repoId}/versions/{versionId}/tcp": { + "post": { + "tags": [ + "TCP" + ], + "summary": "Create a Version TCP", + "description": "Use *Test Repo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in one or more *other* Git repositories", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Project" + }, + "required": true, + "description": "The ID of the Project", + "name": "repoId", + "in": "path" + }, + { + "schema": { + "type": "string", + "pattern": "[\\d]+", + "description": "The ID of the Version" + }, + "required": true, + "description": "The ID of the Version", + "name": "versionId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestRepoTcpInput" + } + } + } + }, + "responses": { + "200": { + "description": "Tests ordered by TCP score. The tests most likely to fail come first.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepoTestTcpOutput" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "415": { + "description": "Unsupported Media Type", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + } + } + } + } + } + } + } + } +} diff --git a/testdata/typescript-1800-loc/old b/testdata/typescript-1800-loc/old new file mode 100644 index 0000000..b64fb75 --- /dev/null +++ b/testdata/typescript-1800-loc/old @@ -0,0 +1,1559 @@ +import type { OpenAPIObject } from 'openapi3-ts/oas31'; + +export const openapi: OpenAPIObject = { + openapi: '3.1.0', + info: { + title: 'Test Bank API', + version: '0.1.0', + description: + 'The Test Bank API provides the following functionality:\n\n* Store changesets (training data)\n* Store verdicts (training data)\n* Request a TCP', + }, + servers: [ + { + url: 'http://localhost:3001', + }, + ], + tags: [ + { + name: 'Projects', + description: 'Project related endpoints', + }, + { + name: 'Repos', + description: 'Repo related endpoints', + }, + { + name: 'Versions', + description: 'Version related endpoints', + }, + { + name: 'Changesets', + description: 'Changeset related endpoints', + }, + { + name: 'Verdicts', + description: 'Verdict related endpoints', + }, + { + name: 'TCP', + description: 'TCP related endpoints', + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + schemas: { + ProjectInsert: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The display name of the project.', + }, + }, + required: ['name'], + description: 'The properties of a project to create.', + }, + RepoInsert: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The display name of the repo.', + }, + }, + required: ['name'], + description: 'The properties of a repo to create.', + }, + VersionRow: { + type: 'object', + properties: { + id: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Version', + }, + name: { + type: 'string', + description: 'The display name of the version.', + }, + }, + required: ['id', 'name'], + description: 'The created version.', + }, + VersionInsert: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The display name of the version.', + }, + }, + required: ['name'], + description: 'The properties of a version to create.', + }, + RepoTest: { + type: 'object', + properties: { + path: { + type: 'string', + description: 'The path to the test file, relative to the root of the git repo.', + }, + line: { + type: 'integer', + description: 'The line number where the test is defined. This is 1-indexed.', + }, + name: { + type: 'string', + description: + 'The name of the test. This is purely informational and will be shown in the UI.', + }, + }, + required: ['path', 'line', 'name'], + description: 'A single test case originating from a file in a git repo.', + }, + RepoTestTcpOutput: { + type: 'object', + properties: { + orderedRepoTests: { + type: 'array', + items: { + $ref: '#/components/schemas/RepoTest', + }, + }, + }, + required: ['orderedRepoTests'], + }, + TestLine: { + type: 'object', + properties: { + line: { + type: 'integer', + description: 'The line number where the test is defined. This is 1-indexed.', + }, + name: { + type: 'string', + description: + 'The name of the test. This is purely informational and will be shown in the UI.', + }, + }, + required: ['line', 'name'], + description: 'A test line describes a single test, defined on a specific line in a file.', + }, + TestFile: { + type: 'object', + properties: { + path: { + type: 'string', + }, + testLines: { + type: 'array', + items: { + $ref: '#/components/schemas/TestLine', + }, + }, + }, + required: ['path', 'testLines'], + description: 'A test file is a file containing one or more tests.', + }, + TestRepoTcpInput: { + type: 'object', + properties: { + testFiles: { + type: 'array', + items: { + $ref: '#/components/schemas/TestFile', + }, + }, + }, + required: ['testFiles'], + description: + 'TCP input for repos where automated tests and SUT are stored in different repos.', + }, + ApfdMetric: { + type: 'object', + properties: { + apfd: { + type: 'number', + }, + apfdData: { + type: 'array', + items: { + type: 'number', + }, + }, + }, + required: ['apfd', 'apfdData'], + }, + ApfdMetrics: { + type: 'object', + properties: { + identity: { + $ref: '#/components/schemas/ApfdMetric', + }, + optimal: { + $ref: '#/components/schemas/ApfdMetric', + }, + worst: { + $ref: '#/components/schemas/ApfdMetric', + }, + random: { + $ref: '#/components/schemas/ApfdMetric', + }, + }, + required: ['identity', 'optimal', 'worst', 'random'], + }, + VerdictsOutput: { + type: 'object', + properties: { + metrics: { + $ref: '#/components/schemas/ApfdMetrics', + }, + }, + required: ['metrics'], + }, + ExternalTest: { + type: 'object', + properties: { + externalId: { + type: 'string', + description: + 'The external ID of the test. This must be specified if the test is not an automated test under source control. The ID must be unique within the project.', + }, + name: { + type: 'string', + description: + 'The name of the test. This is purely informational and will be shown in the UI.', + }, + }, + required: ['externalId', 'name'], + description: 'A single test case originating from outside of a git repo.', + }, + ExternalVerdict: { + type: 'object', + properties: { + externalTest: { + $ref: '#/components/schemas/ExternalTest', + }, + status: { + type: 'string', + enum: ['passed', 'failed', 'unknown'], + description: 'The status of the test.', + }, + }, + required: ['externalTest', 'status'], + description: 'The verdict of an external test, i.e. whether it passed or failed.', + }, + ChangesetOutput: { + type: 'object', + properties: { + repoId: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + changesetId: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Changeset', + }, + }, + required: ['repoId', 'changesetId'], + }, + LineChange: { + type: 'object', + properties: { + oldLine: { + type: 'integer', + description: + 'The line number before the change. 0 means the line did not exist before the change (it has been added).', + }, + newLine: { + type: 'integer', + description: + 'The line number after the change. 0 means the line did not exist after the change (it has been removed).', + }, + cyclomate: { + type: 'boolean', + description: + 'Whether the new line adds cyclomatic complexity. For example, if it is an if/for statement.', + }, + }, + required: ['oldLine', 'newLine'], + description: + 'A line change describes a change to a line between two git revisions. Line numbers are 1-indexed.', + }, + FileChange: { + type: 'object', + properties: { + oldPath: { + type: 'string', + description: + 'The path to the file before the change. The path is relative to the root of the git repo.', + }, + newPath: { + type: 'string', + description: + 'The path to the file after the change. The path is relative to the root of the git repo.', + }, + lineChanges: { + type: 'array', + items: { + $ref: '#/components/schemas/LineChange', + }, + description: 'The changed lines.', + }, + }, + required: ['oldPath', 'newPath', 'lineChanges'], + description: 'A file change describes a change to a file between two git revisions.', + }, + Changeset: { + type: 'object', + properties: { + parentSha: { + type: 'string', + description: 'Git commit SHA', + }, + sha: { + type: 'string', + description: 'Git commit SHA', + }, + fileChanges: { + type: 'array', + items: { + $ref: '#/components/schemas/FileChange', + }, + }, + loc: { + type: 'integer', + }, + }, + required: ['parentSha', 'sha', 'fileChanges', 'loc'], + description: 'A changeset describes the changes between two git revisions.', + }, + RepoVerdict: { + type: 'object', + properties: { + repoTest: { + $ref: '#/components/schemas/RepoTest', + }, + status: { + type: 'string', + enum: ['passed', 'failed', 'unknown'], + description: 'The status of the test.', + }, + }, + required: ['repoTest', 'status'], + description: 'The verdict of a repo test, i.e. whether it passed or failed.', + }, + MonorepoTcpInput: { + type: 'object', + properties: { + changeset: { + $ref: '#/components/schemas/Changeset', + }, + testFiles: { + type: 'array', + items: { + $ref: '#/components/schemas/TestFile', + }, + }, + }, + required: ['changeset', 'testFiles'], + description: + 'TCP input for monorepos where automated tests and code for SUT are stored in a single repo.', + }, + }, + parameters: {}, + }, + paths: { + '/api/v1/projects': { + post: { + tags: ['Projects'], + summary: 'Create a new Project', + description: 'Create a new Project.', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ProjectInsert', + }, + }, + 'application/x-www-form-urlencoded': { + schema: { + $ref: '#/components/schemas/ProjectInsert', + }, + }, + }, + }, + responses: { + '200': { + description: 'The new Project', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + name: { + type: 'string', + }, + apiToken: { + type: 'string', + }, + }, + required: ['id', 'name', 'apiToken'], + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/projects/{projectId}': { + get: { + tags: ['Projects'], + summary: 'Get information about a project', + description: 'Retrieves a project that has been previously created.', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'projectId', + in: 'path', + }, + ], + responses: { + '200': { + description: 'Project information', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + name: { + type: 'string', + }, + }, + required: ['id', 'name'], + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/projects/{projectId}/repos': { + post: { + tags: ['Repos'], + summary: 'Create a new Repo', + description: 'Create a new Repo underneath a project.', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'projectId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/RepoInsert', + }, + }, + 'application/x-www-form-urlencoded': { + schema: { + $ref: '#/components/schemas/RepoInsert', + }, + }, + }, + }, + responses: { + '200': { + description: 'The new Repo', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + name: { + type: 'string', + }, + }, + required: ['id', 'name'], + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/projects/{projectId}/versions': { + post: { + tags: ['Versions'], + summary: 'Create a new Version', + description: 'Create a new Version underneath a project.', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'projectId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/VersionInsert', + }, + }, + 'application/x-www-form-urlencoded': { + schema: { + $ref: '#/components/schemas/VersionInsert', + }, + }, + }, + }, + responses: { + '200': { + description: 'The new Version', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/VersionRow', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/projects/{projectId}/versions/{versionId}/tcp': { + post: { + tags: ['TCP'], + summary: 'Create a Test Repo TCP', + description: + 'Use *Test Repo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in one or more *other* Git repositories', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'projectId', + in: 'path', + }, + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Version', + }, + required: true, + description: 'The ID of the Version', + name: 'versionId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/TestRepoTcpInput', + }, + }, + }, + }, + responses: { + '200': { + description: 'Tests ordered by TCP score. The tests most likely to fail come first.', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/RepoTestTcpOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/projects/{projectId}/versions/{versionId}/verdicts': { + post: { + tags: ['Verdicts'], + summary: 'Store external verdicts', + description: + 'Store verdicts for external tests. \n\nThis should be used when tests are external to the repo (e.g. manual tests), \nor in a multi-repo setting (automated tests in a separate repo from the SUT).', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'projectId', + in: 'path', + }, + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Version', + }, + required: true, + description: 'The ID of the Version', + name: 'versionId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/ExternalVerdict', + }, + description: + 'The verdicts in the order they were executed. If they were executed in parallel, the order should be the devised order by a TCP.', + }, + }, + }, + }, + responses: { + '200': { + description: 'The result of storing the verdicts', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/VerdictsOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/repos/{repoId}/changesets': { + post: { + tags: ['Changesets'], + summary: 'Store a new changeset.', + description: + 'A changeset represents the changes between two revisions of a git repository.\nOptionally specify a versionId query parameter to attach it to a version.\n', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'repoId', + in: 'path', + }, + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Version', + }, + required: false, + description: 'The ID of the Version', + name: 'versionId', + in: 'query', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Changeset', + }, + }, + }, + }, + responses: { + '200': { + description: 'The created changeset', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ChangesetOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '409': { + description: 'Conflict. The changeset already exists.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/repos/{repoId}/changesets/{changesetId}/verdicts': { + post: { + tags: ['Verdicts'], + summary: 'Store repo verdicts', + description: 'Store verdicts for tests stored in a Git repo.', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'repoId', + in: 'path', + }, + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Changeset', + }, + required: true, + description: 'The ID of the Changeset', + name: 'changesetId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/RepoVerdict', + }, + description: + 'The verdicts in the order they were executed. If they were executed in parallel, the order should be the devised order by a TCP.', + }, + }, + }, + }, + responses: { + '200': { + description: 'The result of storing the verdicts', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/VerdictsOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/repos/{repoId}/tcp': { + post: { + tags: ['TCP'], + summary: 'Create a Monorepo TCP', + description: + 'Use *Monorepo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in the *same* Git repositories', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'repoId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/MonorepoTcpInput', + }, + }, + }, + }, + responses: { + '200': { + description: 'Tests ordered by TCP score. The tests most likely to fail come first.', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/RepoTestTcpOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + '/api/v1/repos/{repoId}/versions/{versionId}/tcp': { + post: { + tags: ['TCP'], + summary: 'Create a Version TCP', + description: + 'Use *Test Repo TCP* when:\n\n* Tests are stored in a Git repository\n* The SUT is stored in one or more *other* Git repositories', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Project', + }, + required: true, + description: 'The ID of the Project', + name: 'repoId', + in: 'path', + }, + { + schema: { + type: 'string', + pattern: '[\\d]+', + description: 'The ID of the Version', + }, + required: true, + description: 'The ID of the Version', + name: 'versionId', + in: 'path', + }, + ], + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/TestRepoTcpInput', + }, + }, + }, + }, + responses: { + '200': { + description: 'Tests ordered by TCP score. The tests most likely to fail come first.', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/RepoTestTcpOutput', + }, + }, + }, + }, + '404': { + description: 'Not Found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '415': { + description: 'Unsupported Media Type', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '422': { + description: 'Unprocessable Entity', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + '500': { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/tfidf_cosine_similarity.go b/tfidf_cosine_similarity.go index c4f9988..3c67798 100644 --- a/tfidf_cosine_similarity.go +++ b/tfidf_cosine_similarity.go @@ -71,4 +71,3 @@ func count(key string, a []string) int { } return count } -