feat: support set a store as readonly (#269)

This commit is contained in:
Rick 2023-11-12 07:31:15 +08:00 committed by GitHub
parent b8bf7ffcbe
commit af92ea623d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1076 additions and 382 deletions

View File

@ -111,7 +111,11 @@ jobs:
- name: Core Image - name: Core Image
run: GOPROXY=direct IMG_TOOL=docker make build-image run: GOPROXY=direct IMG_TOOL=docker make build-image
- name: Run e2e - name: Run e2e
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
run: | run: |
sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod u+x /usr/local/bin/docker-compose
make test-e2e make test-e2e
- name: Operator Image - name: Operator Image
run: cd operator && make docker-build run: cd operator && make docker-build
@ -136,4 +140,4 @@ jobs:
sudo atest service install sudo atest service install
sudo atest service restart sudo atest service restart
- name: Test - name: Test
run: make test-ui test-e2e run: make test-ui

View File

@ -1,3 +1,8 @@
# See https://www.gitpod.io/docs/configure/workspaces
tasks: tasks:
- init: make build - init: make init-env
command: docker run --pull always --network host ghcr.io/linuxsuren/api-testing:master before: IMG_TOOL=docker GOPROXY= make build-ext copy-ext build-image
command: cd console/atest-ui/ && npm i
ports:
- port: 5713 # console interactive port

View File

@ -6,21 +6,27 @@ HELM_VERSION?=v0.0.2
APP_VERSION?=v0.0.13 APP_VERSION?=v0.0.13
HELM_REPO?=docker.io/linuxsuren HELM_REPO?=docker.io/linuxsuren
fmt:
go fmt ./...
cd extensions/store-etcd && go fmt ./...
cd extensions/store-git && go fmt ./...
cd extensions/store-orm && go fmt ./...
cd extensions/store-s3 && go fmt ./...
build: build:
mkdir -p bin mkdir -p bin
rm -rf bin/atest rm -rf bin/atest
go build ${TOOLEXEC} -a -o bin/atest main.go go build ${TOOLEXEC} -a -o bin/atest main.go
build-ext: build-ext-git build-ext-orm build-ext-s3 build-ext: build-ext-git build-ext-orm build-ext-s3 build-ext-etcd
build-ext-git: build-ext-git:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-git extensions/store-git/main.go CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-git extensions/store-git/main.go
build-ext-orm: build-ext-orm:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-orm extensions/store-orm/main.go CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-orm extensions/store-orm/main.go
build-ext-etcd: build-ext-etcd:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-etcd extensions/store-etcd/main.go CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-etcd extensions/store-etcd/main.go
build-ui:
cd console/atest-ui && npm i && npm run build-only
build-ext-s3: build-ext-s3:
CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-s3 extensions/store-s3/main.go CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/atest-store-s3 extensions/store-s3/main.go
build-ui:
cd console/atest-ui && npm i && npm run build-only
embed-ui: embed-ui:
cd console/atest-ui && npm i && npm run build-only cd console/atest-ui && npm i && npm run build-only
cp console/atest-ui/dist/index.html cmd/data/index.html cp console/atest-ui/dist/index.html cmd/data/index.html
@ -98,7 +104,7 @@ test-operator:
test-all-backend: test test-collector test-store-orm test-store-s3 test-store-git test-store-etcd test-all-backend: test test-collector test-store-orm test-store-s3 test-store-git test-store-etcd
test-all: test-all-backend test-ui test-all: test-all-backend test-ui
test-e2e: test-e2e:
cd extensions/e2e && docker-compose build && docker-compose up && docker-compose down cd extensions/e2e && ./start.sh
install-precheck: install-precheck:
cp .github/pre-commit .git/hooks/pre-commit cp .github/pre-commit .git/hooks/pre-commit
@ -151,3 +157,7 @@ install-tool:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
hd i protoc-gen-grpc-web hd i protoc-gen-grpc-web
hd i protoc-gen-grpc-gateway hd i protoc-gen-grpc-gateway
init-env:
curl https://linuxsuren.github.io/tools/install.sh|bash
hd i cli/cli
gh extension install linuxsuren/gh-dev

View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"element-plus": "^2.3.7", "element-plus": "^2.3.7",
"intro.js": "^7.0.1", "intro.js": "^7.0.1",
"jsonpath-plus": "^7.2.0",
"skywalking-client-js": "^0.10.0", "skywalking-client-js": "^0.10.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
@ -8747,6 +8748,14 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/jsonpath-plus": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/jsprim": { "node_modules/jsprim": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
@ -18936,6 +18945,11 @@
"universalify": "^2.0.0" "universalify": "^2.0.0"
} }
}, },
"jsonpath-plus": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA=="
},
"jsprim": { "jsprim": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",

View File

@ -18,6 +18,7 @@
"dependencies": { "dependencies": {
"element-plus": "^2.3.7", "element-plus": "^2.3.7",
"intro.js": "^7.0.1", "intro.js": "^7.0.1",
"jsonpath-plus": "^7.2.0",
"skywalking-client-js": "^0.10.0", "skywalking-client-js": "^0.10.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",

View File

@ -10,7 +10,7 @@ import { ElTree, ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { Edit, Share } from '@element-plus/icons-vue' import { Edit, Share } from '@element-plus/icons-vue'
import type { Suite } from './types' import type { Suite } from './types'
import { GetLastTestCaseLocation, SetLastTestCaseLocation } from './views/cache' import { Cache } from './views/cache'
import { DefaultResponseProcess } from './views/net' import { DefaultResponseProcess } from './views/net'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ClientMonitor from 'skywalking-client-js' import ClientMonitor from 'skywalking-client-js'
@ -20,11 +20,12 @@ import setAsDarkTheme from './theme'
const { t } = useI18n() const { t } = useI18n()
const asDarkMode = ref(false) const asDarkMode = ref(Cache.GetPreference().darkTheme)
function switchAppMode() setAsDarkTheme(asDarkMode.value)
{ watch(asDarkMode, Cache.WatchDarkTheme)
watch(asDarkMode, () => {
setAsDarkTheme(asDarkMode.value) setAsDarkTheme(asDarkMode.value)
} })
interface Tree { interface Tree {
id: string id: string
@ -38,14 +39,13 @@ interface Tree {
const testCaseName = ref('') const testCaseName = ref('')
const testSuite = ref('') const testSuite = ref('')
const store = ref('')
const testSuiteKind = ref('') const testSuiteKind = ref('')
const handleNodeClick = (data: Tree) => { const handleNodeClick = (data: Tree) => {
if (data.children) { if (data.children) {
viewName.value = 'testsuite' viewName.value = 'testsuite'
testSuite.value = data.label testSuite.value = data.label
store.value = data.store
testSuiteKind.value = data.kind testSuiteKind.value = data.kind
Cache.SetCurrentStore(data.store)
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
@ -74,10 +74,10 @@ const handleNodeClick = (data: Tree) => {
} }
}) })
} else { } else {
SetLastTestCaseLocation(data.parentID, data.id) Cache.SetLastTestCaseLocation(data.parentID, data.id)
testCaseName.value = data.label testCaseName.value = data.label
testSuite.value = data.parent testSuite.value = data.parent
store.value = data.store Cache.SetCurrentStore(data.store)
testSuiteKind.value = data.kind testSuiteKind.value = data.kind
viewName.value = 'testcase' viewName.value = 'testcase'
} }
@ -141,15 +141,16 @@ function loadStores() {
.then(async (d) => { .then(async (d) => {
stores.value = d.data stores.value = d.data
data.value = [] as Tree[] data.value = [] as Tree[]
Cache.SetStores(d.data)
for (const item of d.data) { for (const item of d.data) {
if (item.ready) { if (item.ready && !item.disabled) {
await loadTestSuites(item.name)() await loadTestSuites(item.name)()
} }
} }
if (data.value.length > 0) { if (data.value.length > 0) {
const key = GetLastTestCaseLocation() const key = Cache.GetLastTestCaseLocation()
let targetSuite = {} as Tree let targetSuite = {} as Tree
let targetChild = {} as Tree let targetChild = {} as Tree
@ -182,7 +183,7 @@ function loadStores() {
treeRef.value!.setCurrentKey(targetChild.id) treeRef.value!.setCurrentKey(targetChild.id)
treeRef.value!.setCheckedKeys([targetChild.id], false) treeRef.value!.setCheckedKeys([targetChild.id], false)
testSuite.value = targetSuite.label testSuite.value = targetSuite.label
store.value = targetSuite.store Cache.SetCurrentStore(targetSuite.store )
testSuiteKind.value = targetChild.kind testSuiteKind.value = targetChild.kind
} else { } else {
viewName.value = "" viewName.value = ""
@ -311,7 +312,7 @@ watch(viewName, (val) => {
customTags: [{ customTags: [{
key: 'theme', value: asDarkMode.value ? 'dark' : 'light' key: 'theme', value: asDarkMode.value ? 'dark' : 'light'
}, { }, {
key: 'store', value: store.value key: 'store', value: Cache.GetCurrentStore().name
}] }]
}); });
}) })
@ -332,7 +333,7 @@ const suiteKinds = [{
<el-button type="primary" :icon="Edit" @click="viewName = 'secret'" data-intro="Manage the secrets."/> <el-button type="primary" :icon="Edit" @click="viewName = 'secret'" data-intro="Manage the secrets."/>
<el-button type="primary" :icon="Share" @click="viewName = 'store'" data-intro="Manage the store backends." /> <el-button type="primary" :icon="Share" @click="viewName = 'store'" data-intro="Manage the store backends." />
<el-form-item label="Dark Mode" style="margin-left:20px;"> <el-form-item label="Dark Mode" style="margin-left:20px;">
<el-switch type="primary" data-intro="Switch light and dark modes" v-model="asDarkMode" @click="switchAppMode"/> <el-switch type="primary" data-intro="Switch light and dark modes" v-model="asDarkMode" />
</el-form-item> </el-form-item>
</el-header> </el-header>
@ -369,7 +370,6 @@ const suiteKinds = [{
/> />
<TestCase <TestCase
v-else-if="viewName === 'testcase'" v-else-if="viewName === 'testcase'"
:store="store"
:suite="testSuite" :suite="testSuite"
:kindName="testSuiteKind" :kindName="testSuiteKind"
:name="testCaseName" :name="testCaseName"
@ -379,7 +379,6 @@ const suiteKinds = [{
<TestSuite <TestSuite
v-else-if="viewName === 'testsuite'" v-else-if="viewName === 'testsuite'"
:name="testSuite" :name="testSuite"
:store="store"
@updated="loadStores" @updated="loadStores"
data-intro="This is the test suite editor. You can edit the test suite here." data-intro="This is the test suite editor. You can edit the test suite here."
/> />

View File

@ -38,6 +38,7 @@
"plugin": "Plugin", "plugin": "Plugin",
"pluginName": "Plugin Name", "pluginName": "Plugin Name",
"pluginURL": "Plugin URL", "pluginURL": "Plugin URL",
"disabled": "Disabled",
"status": "Status", "status": "Status",
"operations": "Operations", "operations": "Operations",
"storageLocation": "Storage Location", "storageLocation": "Storage Location",

View File

@ -22,7 +22,9 @@ const emptyStore = function() {
properties: [{ properties: [{
key: '', key: '',
value: '' value: ''
}] }],
disabled: false,
readonly: false
} as Store } as Store
} }
const stores = ref([] as Store[]) const stores = ref([] as Store[])
@ -38,6 +40,8 @@ interface Store {
username: string username: string
password: string password: string
ready: boolean ready: boolean
disabled: boolean
readonly: boolean
kind: { kind: {
name: string name: string
url: string url: string
@ -86,6 +90,7 @@ function editStore(name: string) {
stores.value.forEach((e: Store) => { stores.value.forEach((e: Store) => {
if (e.name === name) { if (e.name === name) {
setStoreForm(e) setStoreForm(e)
return
} }
}) })
createAction.value = false createAction.value = false
@ -97,7 +102,13 @@ function setStoreForm(store: Store) {
storeForm.username = store.username storeForm.username = store.username
storeForm.password = store.password storeForm.password = store.password
storeForm.kind = store.kind storeForm.kind = store.kind
storeForm.disabled = store.disabled
storeForm.readonly = store.readonly
storeForm.properties = store.properties storeForm.properties = store.properties
storeForm.properties.push({
key: '',
value: ''
})
} }
function addStore() { function addStore() {
@ -107,7 +118,9 @@ function addStore() {
} }
const rules = reactive<FormRules<Store>>({ const rules = reactive<FormRules<Store>>({
name: [{ required: true, message: 'Name is required', trigger: 'blur' }] name: [{ required: true, message: 'Name is required', trigger: 'blur' }],
url: [{ required: true, message: 'URL is required', trigger: 'blur' }],
"kind.name": [{ required: true, message: 'Plugin is required', trigger: 'blur' }]
}) })
const submitForm = async (formEl: FormInstance | undefined) => { const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
@ -166,7 +179,7 @@ function storeVerify(formEl: FormInstance | undefined) {
} }
}) })
.then((e) => { .then((e) => {
if (e.success) { if (e.ready) {
ElMessage({ ElMessage({
message: 'Verified!', message: 'Verified!',
type: 'success' type: 'success'
@ -284,6 +297,9 @@ function updateKeys() {
<el-form-item :label="t('field.pluginURL')" prop="plugin"> <el-form-item :label="t('field.pluginURL')" prop="plugin">
<el-input v-model="storeForm.kind.url" test-id="store-form-plugin" /> <el-input v-model="storeForm.kind.url" test-id="store-form-plugin" />
</el-form-item> </el-form-item>
<el-form-item :label="t('field.disabled')" prop="disabled">
<el-switch v-model="storeForm.disabled" />
</el-form-item>
<el-form-item :label="t('field.properties')" prop="properties"> <el-form-item :label="t('field.properties')" prop="properties">
<el-table :data="storeForm.properties" style="width: 100%"> <el-table :data="storeForm.properties" style="width: 100%">
<el-table-column label="Key" width="180"> <el-table-column label="Key" width="180">

View File

@ -1,26 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Edit, Delete } from '@element-plus/icons-vue' import { Edit, Delete, Search } from '@element-plus/icons-vue'
import JsonViewer from 'vue-json-viewer' import JsonViewer from 'vue-json-viewer'
import type { Pair, TestResult, TestCaseWithSuite } from './types' import type { Pair, TestResult, TestCaseWithSuite } from './types'
import { NewSuggestedAPIsQuery, CreateFilter, GetHTTPMethods, FlattenObject } from './types' import { NewSuggestedAPIsQuery, CreateFilter, GetHTTPMethods, FlattenObject } from './types'
import { GetTestCaseResponseCache, SetTestCaseResponseCache } from './cache' import { Cache } from './cache'
import type { TestCaseResponse } from './cache' import type { TestCaseResponse } from './cache'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { JSONPath } from 'jsonpath-plus'
const { t } = useI18n() const { t } = useI18n()
const props = defineProps({ const props = defineProps({
name: String, name: String,
suite: String, suite: String,
store: String,
kindName: String, kindName: String,
}) })
const store = Cache.GetCurrentStore()
const emit = defineEmits(['updated']) const emit = defineEmits(['updated'])
let querySuggestedAPIs = NewSuggestedAPIsQuery(props.store!, props.suite!) let querySuggestedAPIs = NewSuggestedAPIsQuery(store.name!, props.suite!)
const testResultActiveTab = ref('output') const testResultActiveTab = ref(Cache.GetPreference().responseActiveTab)
watch(testResultActiveTab, Cache.WatchResponseActiveTab)
const requestLoading = ref(false) const requestLoading = ref(false)
const testResult = ref({ header: [] as Pair[] } as TestResult) const testResult = ref({ header: [] as Pair[] } as TestResult)
const sendRequest = async () => { const sendRequest = async () => {
@ -34,7 +37,7 @@ const sendRequest = async () => {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
suite: suite, suite: suite,
@ -61,9 +64,10 @@ const sendRequest = async () => {
} }
if (e.body !== '') { if (e.body !== '') {
testResult.value.bodyObject = JSON.parse(e.body) testResult.value.bodyObject = JSON.parse(e.body)
testResult.value.originBodyObject = JSON.parse(e.body)
} }
SetTestCaseResponseCache(suite + '-' + name, { Cache.SetTestCaseResponseCache(suite + '-' + name, {
body: testResult.value.bodyObject, body: testResult.value.bodyObject,
output: e.output, output: e.output,
statusCode: testResult.value.statusCode statusCode: testResult.value.statusCode
@ -77,16 +81,31 @@ const sendRequest = async () => {
requestLoading.value = false requestLoading.value = false
ElMessage.error('Oops, ' + e) ElMessage.error('Oops, ' + e)
testResult.value.bodyObject = JSON.parse(e.body) testResult.value.bodyObject = JSON.parse(e.body)
testResult.value.originBodyObject = JSON.parse(e.body)
}) })
} }
const responseBodyFilterText = ref('')
function responseBodyFilter() {
if (responseBodyFilterText.value === '') {
testResult.value.bodyObject = testResult.value.originBodyObject
} else {
const query = JSONPath({
path: responseBodyFilterText.value,
json: testResult.value.originBodyObject,
resultType: 'value'
})
testResult.value.bodyObject = query[0]
}
}
const parameterDialogOpened = ref(false) const parameterDialogOpened = ref(false)
const parameters = ref([] as Pair[]) const parameters = ref([] as Pair[])
function openParameterDialog() { function openParameterDialog() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
name: props.suite name: props.suite
@ -113,7 +132,7 @@ function generateCode() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
TestSuite: suite, TestSuite: suite,
@ -200,7 +219,7 @@ function load() {
} }
// load cache // load cache
const cache = GetTestCaseResponseCache(suite + '-' + name) const cache = Cache.GetTestCaseResponseCache(suite + '-' + name)
if (cache.body) { if (cache.body) {
testResult.value.bodyObject = cache.body testResult.value.bodyObject = cache.body
testResult.value.output = cache.output testResult.value.output = cache.output
@ -210,11 +229,12 @@ function load() {
testResult.value.output = '' testResult.value.output = ''
testResult.value.statusCode = 0 testResult.value.statusCode = 0
} }
testResult.value.originBodyObject = testResult.value.bodyObject
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
suite: suite, suite: suite,
@ -291,7 +311,7 @@ function saveTestCase(tip: boolean = true) {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify(testCaseWithSuite.value) body: JSON.stringify(testCaseWithSuite.value)
} }
@ -318,7 +338,7 @@ function deleteTestCase() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
suite: suite, suite: suite,
@ -366,7 +386,8 @@ watch(currentCodeGenerator, () => {
}) })
const options = GetHTTPMethods() const options = GetHTTPMethods()
const activeName = ref('body') const requestActiveTab = ref(Cache.GetPreference().requestActiveTab)
watch(requestActiveTab, Cache.WatchRequestActiveTab)
function bodyFiledExpectChange() { function bodyFiledExpectChange() {
const data = testCaseWithSuite.value.data.response.bodyFieldsExpect const data = testCaseWithSuite.value.data.response.bodyFieldsExpect
@ -468,7 +489,7 @@ const pupularHeaders = ref([] as Pair[])
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
} }
fetch('/server.Runner/PopularHeaders', requestOptions) fetch('/server.Runner/PopularHeaders', requestOptions)
@ -494,8 +515,11 @@ const queryPupularHeaders = (queryString: string, cb: (arg: any) => void) => {
<el-header style="padding-left: 5px"> <el-header style="padding-left: 5px">
<div style="margin-bottom: 5px"> <div style="margin-bottom: 5px">
<el-button type="primary" @click="saveTestCase" :icon="Edit" :loading="saveLoading" <el-button type="primary" @click="saveTestCase" :icon="Edit" :loading="saveLoading"
>{{ t('button.save') }}</el-button disabled v-if="store.readOnly"
> >{{ t('button.save') }}</el-button>
<el-button type="primary" @click="saveTestCase" :icon="Edit" :loading="saveLoading"
v-if="!store.readOnly"
>{{ t('button.save') }}</el-button>
<el-button type="primary" @click="deleteTestCase" :icon="Delete">{{ t('button.delete') }}</el-button> <el-button type="primary" @click="deleteTestCase" :icon="Delete">{{ t('button.delete') }}</el-button>
<el-button type="primary" @click="openCodeDialog">{{ t('button.generateCode') }}</el-button> <el-button type="primary" @click="openCodeDialog">{{ t('button.generateCode') }}</el-button>
</div> </div>
@ -537,7 +561,7 @@ const queryPupularHeaders = (queryString: string, cb: (arg: any) => void) => {
</el-header> </el-header>
<el-main> <el-main>
<el-tabs v-model="activeName" class="demo-tabs"> <el-tabs v-model="requestActiveTab" class="demo-tabs">
<el-tab-pane label="Query" name="query" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'"> <el-tab-pane label="Query" name="query" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
<el-table :data="testCaseWithSuite.data.request.query" style="width: 100%"> <el-table :data="testCaseWithSuite.data.request.query" style="width: 100%">
<el-table-column label="Key" width="180"> <el-table-column label="Key" width="180">
@ -767,6 +791,8 @@ const queryPupularHeaders = (queryString: string, cb: (arg: any) => void) => {
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Body" name="body"> <el-tab-pane label="Body" name="body">
<el-input :prefix-icon="Search" @change="responseBodyFilter" v-model="responseBodyFilterText"
clearable label="dddd" placeholder="$.key" />
<JsonViewer :value="testResult.bodyObject" :expand-depth="5" copyable boxed sort /> <JsonViewer :value="testResult.bodyObject" :expand-depth="5" copyable boxed sort />
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="response-header"> <el-tab-pane name="response-header">

View File

@ -5,16 +5,17 @@ import { Edit } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import type { Suite, TestCase, Pair } from './types' import type { Suite, TestCase, Pair } from './types'
import { NewSuggestedAPIsQuery } from './types' import { NewSuggestedAPIsQuery } from './types'
import { Cache } from './cache'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const props = defineProps({ const props = defineProps({
name: String, name: String,
store: String,
}) })
const store = Cache.GetCurrentStore()
const emit = defineEmits(['updated']) const emit = defineEmits(['updated'])
let querySuggestedAPIs = NewSuggestedAPIsQuery(props.store!, props.name!) let querySuggestedAPIs = NewSuggestedAPIsQuery(store.name, props.name!)
const suite = ref({ const suite = ref({
name: '', name: '',
@ -30,12 +31,12 @@ const suite = ref({
} }
} as Suite) } as Suite)
function load() { function load() {
if (!props.name || props.store === "") return if (!props.name || store.name === "") return
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
name: props.name name: props.name
@ -70,7 +71,7 @@ function save() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify(suite.value) body: JSON.stringify(suite.value)
} }
@ -108,7 +109,7 @@ const rules = reactive<FormRules<Suite>>({
function openNewTestCaseDialog() { function openNewTestCaseDialog() {
dialogVisible.value = true dialogVisible.value = true
querySuggestedAPIs = NewSuggestedAPIsQuery(props.store!, props.name!) querySuggestedAPIs = NewSuggestedAPIsQuery(store.name!, props.name!)
} }
const submitForm = async (formEl: FormInstance | undefined) => { const submitForm = async (formEl: FormInstance | undefined) => {
@ -120,7 +121,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
suiteName: props.name, suiteName: props.name,
@ -150,7 +151,7 @@ function del() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
name: props.name name: props.name
@ -174,7 +175,7 @@ function convert() {
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Store-Name': props.store 'X-Store-Name': store.name
}, },
body: JSON.stringify({ body: JSON.stringify({
Generator: 'jmeter', Generator: 'jmeter',
@ -294,7 +295,8 @@ function paramChange() {
<el-divider /> <el-divider />
</div> </div>
<el-button type="primary" @click="save">{{ t('button.save') }}</el-button> <el-button type="primary" @click="save" v-if="!store.readOnly">{{ t('button.save') }}</el-button>
<el-button type="primary" @click="save" disabled v-if="store.readOnly">{{ t('button.save') }}</el-button>
<el-button type="primary" @click="del" test-id="suite-del-but">{{ t('button.delete') }}</el-button> <el-button type="primary" @click="del" test-id="suite-del-but">{{ t('button.delete') }}</el-button>
<el-button type="primary" @click="openNewTestCaseDialog" :icon="Edit" test-id="open-new-case-dialog">{{ t('button.newtestcase') }}</el-button> <el-button type="primary" @click="openNewTestCaseDialog" :icon="Edit" test-id="open-new-case-dialog">{{ t('button.newtestcase') }}</el-button>
<el-button type="primary" @click="convert" test-id="convert">{{ t('button.export') }}</el-button> <el-button type="primary" @click="convert" test-id="convert">{{ t('button.export') }}</el-button>

View File

@ -4,6 +4,22 @@ export interface TestCaseResponse {
statusCode: number statusCode: number
} }
interface Store {
name: string
readOnly: boolean
}
interface Stores {
current: string
items: Store[]
}
export interface Preference {
darkTheme: boolean
requestActiveTab: string,
responseActiveTab: string
}
export function GetTestCaseResponseCache(id: string) { export function GetTestCaseResponseCache(id: string) {
const val = sessionStorage.getItem(id) const val = sessionStorage.getItem(id)
if (val && val !== '') { if (val && val !== '') {
@ -34,3 +50,84 @@ export function SetLastTestCaseLocation(suite: string, testcase: string) {
})) }))
return return
} }
const preferenceKey = "api-testing-preference"
export function GetPreference() {
const val = localStorage.getItem(preferenceKey)
if (val && val !== '') {
return JSON.parse(val)
} else {
return {
darkTheme: false,
requestActiveTab: "body",
responseActiveTab: "body"
} as Preference
}
}
export function SetPreference(preference: Preference) {
localStorage.setItem(preferenceKey, JSON.stringify(preference))
return
}
export function WatchRequestActiveTab(tab: string) {
const preference = GetPreference()
preference.requestActiveTab = tab
SetPreference(preference)
}
function WatchResponseActiveTab(tab: string) {
const preference = GetPreference()
preference.responseActiveTab = tab
SetPreference(preference)
}
function WatchDarkTheme(darkTheme: boolean) {
const preference = GetPreference()
preference.darkTheme = darkTheme
SetPreference(preference)
}
const storeKey = "stores"
function GetCurrentStore() {
const val = sessionStorage.getItem(storeKey)
if (val && val !== '') {
const stores = JSON.parse(val)
for (var i = 0; i < stores.items.length; i++) {
if (stores.items[i].name === stores.current) {
return stores.items[i]
}
}
}
return {}
}
function SetCurrentStore(name: string) {
const val = sessionStorage.getItem(storeKey)
if (val && val !== '') {
const stores = JSON.parse(val)
stores.current = name
SetStores(stores)
}
}
function SetStores(stores: Stores | Store[]) {
if ('current' in stores) {
sessionStorage.setItem(storeKey, JSON.stringify(stores))
} else {
sessionStorage.setItem(storeKey, JSON.stringify({
items: stores
}))
}
return
}
export const Cache = {
GetTestCaseResponseCache,
SetTestCaseResponseCache,
GetLastTestCaseLocation,
SetLastTestCaseLocation,
GetPreference,
WatchRequestActiveTab,
WatchResponseActiveTab,
WatchDarkTheme,
GetCurrentStore, SetStores, SetCurrentStore
}

View File

@ -18,6 +18,9 @@ export interface TestResult {
error: string error: string
statusCode: number statusCode: number
header: Pair[] header: Pair[]
// inner fileds
originBodyObject:{}
} }
export interface Pair { export interface Pair {

View File

@ -198,7 +198,7 @@ tiup playground --db.host 0.0.0.0
mkdir bin mkdir bin
echo "- name: db echo "- name: db
kind: kind:
name: database name: atest-store-orm
url: localhost:7071 url: localhost:7071
url: localhost:4000 url: localhost:4000
username: root username: root
@ -233,15 +233,16 @@ Have a look at the expected configuration below:
- name: s3 - name: s3
url: http://172.11.0.13:30999 # address of the s3 server url: http://172.11.0.13:30999 # address of the s3 server
kind: kind:
name: s3 name: atest-store-s3
url: localhost:7072 # address of the s3 storage extension url: localhost:7072 # address of the s3 storage extension
properties: properties:
accessKeyID: 6e03rIMChrsZ6YZl accessKeyID: 6e03rIMChrsZ6YZl
secretAccessKey: F0xH6o2qRYTyAUyRuXO81B4gj7zUrSaj secretAccessKey: F0xH6o2qRYTyAUyRuXO81B4gj7zUrSaj
sessiontoken: ""
region: cn
disableSSL: true disableSSL: true
forcepathstyle: true forcepathstyle: true
bucket: vm1 bucket: vm1
region: cn
``` ```
### Git Storage ### Git Storage
@ -261,10 +262,13 @@ Have a look at the expected configuration below:
username: linuxsuren username: linuxsuren
password: linuxsuren password: linuxsuren
kind: kind:
name: git name: atest-store-git # the extension binary file name
url: localhost:7074 # address of the git storage extension url: localhost:7074 # address of the git storage extension
properties: properties: # optional properties for specific features
targetPath: . targetPath: . # target path to find YAML files
name: linuxsuren # the name for git commit
email: linuxsuren@github.com # the email address for git commit
insecure: false # whether to use insecure
``` ```
## Secret Server ## Secret Server

View File

@ -0,0 +1,12 @@
version: '3.1'
services:
testing:
image: "linuxsuren.docker.scarf.sh/linuxsuren/api-testing:master"
ports:
- 8080:8080
etcd:
image: "bitnami/etcd:3.5.10"
expose:
- "2379"
environment:
ALLOW_NONE_AUTHENTICATION: "yes"

View File

@ -1,9 +1,6 @@
FROM bitnami/etcd:3.5.10 AS etcd
FROM ghcr.io/linuxsuren/api-testing:master FROM ghcr.io/linuxsuren/api-testing:master
WORKDIR /workspace WORKDIR /workspace
COPY --from=etcd /opt/bitnami/etcd/bin/etcd /usr/local/bin/etcd
COPY . . COPY . .
RUN chmod +x entrypoint.sh RUN chmod +x entrypoint.sh

View File

@ -1,15 +1,92 @@
version: '3.1'
services: services:
testing: testing:
build: . build: .
environment:
GITEE_TOKEN: "$GITEE_TOKEN"
depends_on:
etcd:
condition: service_healthy
mysql:
condition: service_healthy
mariadb:
condition: service_healthy
# postgres:
# condition: service_healthy
# clickhouse:
# condition: service_healthy
# minio:
# condition: service_started
links:
- etcd
- mysql
- mariadb
# - minio
# - postgres
# - clickhouse
etcd:
image: "bitnami/etcd:3.5.10"
expose:
- "2379"
environment: environment:
ALLOW_NONE_AUTHENTICATION: "yes" ALLOW_NONE_AUTHENTICATION: "yes"
# depends_on: healthcheck:
# - etcd test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/2379"]
# links: interval: 3s
# - etcd timeout: 30s
# etcd: retries: 10
# image: "bitnami/etcd:3.5.10" start_period: 3s
# expose: mysql:
# - "2379" image: mysql:8.2.0
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: atest
healthcheck:
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306"]
interval: 3s
timeout: 30s
retries: 10
start_period: 3s
mariadb:
image: mariadb:11.0
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: atest
healthcheck:
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306"]
interval: 3s
timeout: 30s
retries: 10
start_period: 3s
minio:
image: bitnami/minio:2023.11.6
environment:
MINIO_ROOT_USER: root
MINIO_ROOT_PASSWORD: root
MINIO_SERVER_HOST: minio
MINIO_DEFAULT_BUCKETS: bucket
# postgres:
# image: postgres:16.0
# environment: # environment:
# ALLOW_NONE_AUTHENTICATION: "yes" # POSTGRES_USER: root
# POSTGRES_PASSWORD: root
# POSTGRES_DB: atest
# healthcheck:
# test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/5432"]
# interval: 3s
# timeout: 30s
# retries: 10
# start_period: 3s
# clickhouse:
# image: bitnami/clickhouse:23.10.2
# environment:
# CLICKHOUSE_ADMIN_USER: root
# CLICKHOUSE_ADMIN_PASSWORD: root
# CLICKHOUSE_MYSQL_PORT: 9004
# healthcheck:
# test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9004"]
# interval: 3s
# timeout: 30s
# retries: 10
# start_period: 3s

View File

@ -3,10 +3,23 @@ set -e
mkdir -p /root/.config/atest mkdir -p /root/.config/atest
nohup etcd&
nohup atest server& nohup atest server&
atest run -p etcd.yaml kind=orm target=mysql:3306 driver=mysql atest run -p test-suite-common.yaml
atest run -p git.yaml kind=orm target=mariadb:3306 driver=mysql atest run -p test-suite-common.yaml
kind=etcd target=etcd:2379 atest run -p test-suite-common.yaml
if [ -z "$GITEE_TOKEN" ]
then
atest run -p git.yaml
else
echo "found gitee token"
kind=git target=https://gitee.com/linuxsuren/test username=linuxsuren password=$GITEE_TOKEN atest run -p test-suite-common.yaml
fi
# TODO need to fix below cases
# kind=orm target=postgres:5432 driver=postgres atest run -p test-suite-common.yaml
# kind=orm target=clickhouse:9004 driver=mysql dbname=default atest run -p test-suite-common.yaml
# kind=s3 target=minio:9000 atest run -p test-suite-common.yaml
cat /root/.config/atest/stores.yaml cat /root/.config/atest/stores.yaml
exit 0

25
extensions/e2e/start.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
docker-compose version
docker-compose up --build -d
while true
do
docker-compose ps | grep testing
if [ $? -eq 1 ]
then
code=-1
docker-compose logs | grep e2e-testing
docker-compose logs | grep e2e-testing | grep Usage
if [ $? -eq 1 ]
then
code=0
echo "successed"
fi
docker-compose down
set -e
exit $code
fi
sleep 1
done

View File

@ -7,22 +7,43 @@ api: |
param: param:
suiteName: "{{randAlpha 6}}" suiteName: "{{randAlpha 6}}"
caseName: "{{randAlpha 6}}" caseName: "{{randAlpha 6}}"
store: "{{randAlpha 3}}"
items: items:
- name: CreateStore - name: CreateStore
before: before:
items: items:
- httpReady("http://localhost:8080/healthz", 2400) - httpReady("http://localhost:8080/healthz", 2400)
- httpReady("http://localhost:2379/health", 2400)
request: request:
api: /CreateStore api: /CreateStore
method: POST method: POST
body: | body: |
{ {
"name": "etcd", "name": "{{.param.store}}",
"url": "localhost:2379", "url": "{{env "target"}}",
"username": "{{default "root" (env "username")}}",
"password": "{{default "root" (env "password")}}",
"kind": { "kind": {
"name": "atest-store-etcd" "name": "atest-store-{{env "kind"}}"
} },
"properties": [{
"key": "driver",
"value": "{{default "mysql" (env "driver")}}"
}, {
"key": "database",
"value": "{{default "atest" (env "dbname")}}"
}, {
"key": "bucket",
"value": "bucket"
}, {
"key": "region",
"value": "cn"
}, {
"key": "disablessl",
"value": "true"
}, {
"key": "targetPath",
"value": "api-testing"
}]
} }
- name: createSuite - name: createSuite
before: before:
@ -32,7 +53,7 @@ items:
api: /CreateTestSuite api: /CreateTestSuite
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"name": "{{.param.suiteName}}", "name": "{{.param.suiteName}}",
@ -43,7 +64,7 @@ items:
api: /UpdateTestSuite api: /UpdateTestSuite
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"name": "{{.param.suiteName}}", "name": "{{.param.suiteName}}",
@ -58,7 +79,7 @@ items:
api: /GetTestSuite api: /GetTestSuite
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"name": "{{.param.suiteName}}" "name": "{{.param.suiteName}}"
@ -71,7 +92,7 @@ items:
api: /CreateTestCase api: /CreateTestCase
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"suiteName": "{{.param.suiteName}}", "suiteName": "{{.param.suiteName}}",
@ -82,7 +103,7 @@ items:
"method": "POST", "method": "POST",
"header": [{ "header": [{
"name": "X-Store-Name", "name": "X-Store-Name",
"value": "etcd" "value": "{{.param.store}}"
}] }]
} }
} }
@ -92,7 +113,7 @@ items:
api: /UpdateTestCase api: /UpdateTestCase
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"suiteName": "{{.param.suiteName}}", "suiteName": "{{.param.suiteName}}",
@ -103,7 +124,7 @@ items:
"method": "POST", "method": "POST",
"header": [{ "header": [{
"name": "X-Store-Name", "name": "X-Store-Name",
"value": "etcd" "value": "{{.param.store}}"
}], }],
"body": "good" "body": "good"
} }
@ -114,7 +135,7 @@ items:
api: /GetTestCase api: /GetTestCase
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"suite": "{{.param.suiteName}}", "suite": "{{.param.suiteName}}",
@ -128,7 +149,7 @@ items:
api: /DeleteTestCase api: /DeleteTestCase
method: POST method: POST
header: header:
X-Store-Name: etcd X-Store-Name: "{{.param.store}}"
body: | body: |
{ {
"suite": "{{.param.suiteName}}", "suite": "{{.param.suiteName}}",

View File

@ -41,17 +41,15 @@ func NewRootCommand() (c *cobra.Command) {
RunE: opt.runE, RunE: opt.runE,
} }
opt.AddFlags(c.Flags()) opt.AddFlags(c.Flags())
c.Flags().StringVarP(&opt.endpoint, "endpoint", "", "", "The etcd server endpoint")
return return
} }
type options struct { type options struct {
*ext.Extension *ext.Extension
endpoint string
} }
func (o *options) runE(c *cobra.Command, _ []string) (err error) { func (o *options) runE(c *cobra.Command, _ []string) (err error) {
remoteServer := pkg.NewRemoteServer(o.endpoint, pkg.NewRealEtcd()) remoteServer := pkg.NewRemoteServer(pkg.NewRealEtcd())
err = ext.CreateRunner(o.Extension, c, remoteServer) err = ext.CreateRunner(o.Extension, c, remoteServer)
return return
} }

View File

@ -33,19 +33,18 @@ import (
"github.com/linuxsuren/api-testing/pkg/server" "github.com/linuxsuren/api-testing/pkg/server"
"github.com/linuxsuren/api-testing/pkg/testing" "github.com/linuxsuren/api-testing/pkg/testing"
"github.com/linuxsuren/api-testing/pkg/testing/remote" "github.com/linuxsuren/api-testing/pkg/testing/remote"
"github.com/linuxsuren/api-testing/pkg/version"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
type remoteserver struct { type remoteserver struct {
endpoint string
kvFactory KVFactory kvFactory KVFactory
remote.UnimplementedLoaderServer remote.UnimplementedLoaderServer
} }
// NewRemoteServer creates a remote server instance // NewRemoteServer creates a remote server instance
func NewRemoteServer(endpoint string, kvFactory KVFactory) remote.LoaderServer { func NewRemoteServer(kvFactory KVFactory) remote.LoaderServer {
return &remoteserver{ return &remoteserver{
endpoint: endpoint,
kvFactory: kvFactory, kvFactory: kvFactory,
} }
} }
@ -245,8 +244,10 @@ func (s *remoteserver) DeleteTestCase(ctx context.Context, testcase *server.Test
} }
return return
} }
func (s *remoteserver) Verify(ctx context.Context, in *server.Empty) (reply *server.CommonResult, err error) { func (s *remoteserver) Verify(ctx context.Context, in *server.Empty) (reply *server.ExtensionStatus, err error) {
reply = &server.CommonResult{} reply = &server.ExtensionStatus{
Version: version.GetVersion(),
}
var cli SimpleKV var cli SimpleKV
cli, err = s.getClient(ctx) cli, err = s.getClient(ctx)
@ -256,9 +257,18 @@ func (s *remoteserver) Verify(ctx context.Context, in *server.Empty) (reply *ser
} }
defer cli.Close() defer cli.Close()
reply.Success = true // try to connect
if _, err = cli.Get(ctx, keyPrefix, connectTestOption()...); err == nil {
reply.Ready = true
}
return return
} }
func connectTestOption() []clientv3.OpOption {
return []clientv3.OpOption{clientv3.WithLimit(1), clientv3.WithPrefix(),
clientv3.WithCountOnly(), clientv3.WithKeysOnly()}
}
func getTestCase(ctx context.Context, cli SimpleKV, suiteName, caseName string) (testcase *testing.TestCase, index int, testsuite *testing.TestSuite, err error) { func getTestCase(ctx context.Context, cli SimpleKV, suiteName, caseName string) (testcase *testing.TestCase, index int, testsuite *testing.TestSuite, err error) {
index = NotFound index = NotFound
var resp *clientv3.GetResponse var resp *clientv3.GetResponse

View File

@ -39,7 +39,7 @@ func TestRemoteServer(t *testing.T) {
ctx := remote.WithIncomingStoreContext(context.Background(), &atest.Store{ ctx := remote.WithIncomingStoreContext(context.Background(), &atest.Store{
Name: "test", Name: "test",
}) })
remoteServer := NewRemoteServer("endpoint", &fakeKV{ remoteServer := NewRemoteServer(&fakeKV{
data: map[string]string{}, data: map[string]string{},
}) })
@ -128,7 +128,7 @@ func TestRemoteServer(t *testing.T) {
verifyResult, err := remoteServer.Verify(ctx, &server.Empty{}) verifyResult, err := remoteServer.Verify(ctx, &server.Empty{})
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.True(t, verifyResult.Success) assert.True(t, verifyResult.Ready)
} }
} }

View File

@ -41,6 +41,7 @@ import (
"github.com/linuxsuren/api-testing/pkg/testing" "github.com/linuxsuren/api-testing/pkg/testing"
"github.com/linuxsuren/api-testing/pkg/testing/remote" "github.com/linuxsuren/api-testing/pkg/testing/remote"
"github.com/linuxsuren/api-testing/pkg/util" "github.com/linuxsuren/api-testing/pkg/util"
"github.com/linuxsuren/api-testing/pkg/version"
) )
type gitClient struct { type gitClient struct {
@ -250,11 +251,18 @@ func (s *gitClient) DeleteTestCase(ctx context.Context, testcase *server.TestCas
} }
return return
} }
func (s *gitClient) Verify(ctx context.Context, in *server.Empty) (reply *server.CommonResult, err error) { func (s *gitClient) Verify(ctx context.Context, in *server.Empty) (reply *server.ExtensionStatus, err error) {
_, clientErr := s.ListTestSuite(ctx, in) _, clientErr := s.ListTestSuite(ctx, in)
reply = &server.CommonResult{ reply = &server.ExtensionStatus{
Success: clientErr == nil, Ready: clientErr == nil,
Message: util.OKOrErrorMessage(clientErr), Message: util.OKOrErrorMessage(clientErr),
Version: version.GetVersion(),
}
// no git repository allows to write files without authentication
store := remote.GetStoreFromContext(ctx)
if store != nil {
reply.ReadOnly = store.Username == "" || store.Password == ""
} }
return return
} }

View File

@ -61,7 +61,8 @@ func TestGetClient(t *testing.T) {
result, err := gitClient.Verify(ctx, &server.Empty{}) result, err := gitClient.Verify(ctx, &server.Empty{})
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, result.Success) assert.False(t, result.Ready)
assert.True(t, result.ReadOnly)
}) })
t.Run("ListTestSuite", func(t *testing.T) { t.Run("ListTestSuite", func(t *testing.T) {

View File

@ -1,4 +1,5 @@
/** /*
*
MIT License MIT License
Copyright (c) 2023 API Testing Authors. Copyright (c) 2023 API Testing Authors.

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg package pkg
import ( import (

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg_test package pkg_test
import ( import (

View File

@ -21,16 +21,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
package pkg package pkg
import ( import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log"
"strings"
"github.com/linuxsuren/api-testing/pkg/server" "github.com/linuxsuren/api-testing/pkg/server"
"github.com/linuxsuren/api-testing/pkg/testing/remote" "github.com/linuxsuren/api-testing/pkg/testing/remote"
"github.com/linuxsuren/api-testing/pkg/util" "github.com/linuxsuren/api-testing/pkg/util"
"github.com/linuxsuren/api-testing/pkg/version"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
@ -46,9 +50,23 @@ func NewRemoteServer() (s remote.LoaderServer) {
return return
} }
func createDB(user, address, database string) (db *gorm.DB, err error) { func createDB(user, password, address, database, driver string) (db *gorm.DB, err error) {
dsn := fmt.Sprintf("%s:@tcp(%s)/%s?charset=utf8mb4", user, address, database) var dsn string
fmt.Println("try to connect to", dsn) switch driver {
case "mysql", "":
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", user, password, address, database)
case "postgres":
obj := strings.Split(address, ":")
host, port := obj[0], obj[1]
dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", host, user, password, database, port)
case "clickhouse":
dsn = fmt.Sprintf("tcp://%s?database=%s&username=%s&password=%s&read_timeout=10&write_timeout=20", address, database, user, password)
default:
err = fmt.Errorf("invalid database driver %q", driver)
return
}
log.Printf("try to connect to %q", dsn)
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), Logger: logger.Default.LogMode(logger.Info),
}) })
@ -75,13 +93,15 @@ func (s *dbserver) getClient(ctx context.Context) (db *gorm.DB, err error) {
} }
database := "atest" database := "atest"
for key, val := range store.Properties { driver := "mysql"
if key == "database" { if v, ok := store.Properties["database"]; ok && v != "" {
database = val database = v
} }
if v, ok := store.Properties["driver"]; ok && v != "" {
driver = v
} }
if db, err = createDB(store.Username, store.URL, database); err == nil { if db, err = createDB(store.Username, store.Password, store.URL, database, driver); err == nil {
dbCache[store.Name] = db dbCache[store.Name] = db
} }
} }
@ -237,11 +257,12 @@ func (s *dbserver) DeleteTestCase(ctx context.Context, testcase *server.TestCase
return return
} }
func (s *dbserver) Verify(ctx context.Context, in *server.Empty) (reply *server.CommonResult, err error) { func (s *dbserver) Verify(ctx context.Context, in *server.Empty) (reply *server.ExtensionStatus, err error) {
db, clientErr := s.getClient(ctx) db, clientErr := s.getClient(ctx)
reply = &server.CommonResult{ reply = &server.ExtensionStatus{
Success: err == nil && db != nil, Ready: err == nil && db != nil,
Message: util.OKOrErrorMessage(clientErr), Message: util.OKOrErrorMessage(clientErr),
Version: version.GetVersion(),
} }
return return
} }

View File

@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
package pkg package pkg
import ( import (
@ -90,6 +91,6 @@ func TestNewRemoteServer(t *testing.T) {
t.Run("Verify", func(t *testing.T) { t.Run("Verify", func(t *testing.T) {
reply, err := remoteServer.Verify(defaultCtx, nil) reply, err := remoteServer.Verify(defaultCtx, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, reply.Success) assert.False(t, reply.Ready)
}) })
} }

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd package cmd
import ( import (

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd package cmd
import ( import (

View File

@ -100,3 +100,5 @@ require (
trpc.group/trpc-go/trpc-go v1.0.1 // indirect trpc.group/trpc-go/trpc-go v1.0.1 // indirect
trpc.group/trpc/trpc-protocol/pb/go/trpc v1.0.0 // indirect trpc.group/trpc/trpc-protocol/pb/go/trpc v1.0.0 // indirect
) )
replace github.com/linuxsuren/api-testing => ../../.

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg package pkg
import ( import (

View File

@ -1,9 +1,34 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg package pkg
import ( import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"log"
"strings" "strings"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -14,6 +39,7 @@ import (
"github.com/linuxsuren/api-testing/pkg/testing" "github.com/linuxsuren/api-testing/pkg/testing"
"github.com/linuxsuren/api-testing/pkg/testing/remote" "github.com/linuxsuren/api-testing/pkg/testing/remote"
"github.com/linuxsuren/api-testing/pkg/util" "github.com/linuxsuren/api-testing/pkg/util"
"github.com/linuxsuren/api-testing/pkg/version"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -170,11 +196,12 @@ func (s *s3Client) DeleteTestCase(ctx context.Context, testcase *server.TestCase
} }
return return
} }
func (s *s3Client) Verify(ctx context.Context, in *server.Empty) (reply *server.CommonResult, err error) { func (s *s3Client) Verify(ctx context.Context, in *server.Empty) (reply *server.ExtensionStatus, err error) {
_, clientErr := s.ListTestSuite(ctx, in) _, clientErr := s.ListTestSuite(ctx, in)
reply = &server.CommonResult{ reply = &server.ExtensionStatus{
Success: err == nil, Ready: err == nil,
Message: util.OKOrErrorMessage(clientErr), Message: util.OKOrErrorMessage(clientErr),
Version: version.GetVersion(),
} }
return return
} }
@ -189,11 +216,19 @@ func (s *s3Client) getClient(ctx context.Context) (db *s3WithBucket, err error)
} }
options := mapToS3Options(store.Properties) options := mapToS3Options(store.Properties)
if options.AccessKeyID == "" {
options.AccessKeyID = store.Username
}
if options.SecretAccessKey == "" {
options.SecretAccessKey = store.Password
}
log.Println("s3 server", store.URL)
var sess *session.Session var sess *session.Session
sess, err = createClientFromSs3Options(options, store.URL) sess, err = createClientFromSs3Options(options, store.URL)
if err == nil { if err == nil {
svc := s.S3Creator.New(sess) // s3.New(sess) svc := s.S3Creator.New(sess)
db = &s3WithBucket{S3API: svc, bucket: options.Bucket} db = &s3WithBucket{S3API: svc, bucket: options.Bucket}
clientCache[store.Name] = db clientCache[store.Name] = db
} }

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg package pkg
import ( import (
@ -32,10 +56,10 @@ func TestNewRemoteServer(t *testing.T) {
_, err := newRemoteServer(t).ListTestSuite(defaultCtx, nil) _, err := newRemoteServer(t).ListTestSuite(defaultCtx, nil)
assert.NoError(t, err) assert.NoError(t, err)
var result *server.CommonResult var result *server.ExtensionStatus
result, err = newRemoteServer(t).Verify(defaultCtx, &server.Empty{}) result, err = newRemoteServer(t).Verify(defaultCtx, &server.Empty{})
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, result.Success) assert.True(t, result.Ready)
}) })
t.Run("CreateTestSuite", func(t *testing.T) { t.Run("CreateTestSuite", func(t *testing.T) {

View File

@ -90,7 +90,7 @@ func newNoEqualErr(field string, err error) error {
Path: []string{field}, Path: []string{field},
Message: "", Message: "",
} }
if err != nil { if err != nil {
if v, ok := err.(*noEqualErr); ok { if v, ok := err.(*noEqualErr); ok {
noEqerr.Path = append(noEqerr.Path, v.Path...) noEqerr.Path = append(noEqerr.Path, v.Path...)

View File

@ -26,12 +26,14 @@ package extension
import ( import (
"fmt" "fmt"
"net"
"os"
"github.com/linuxsuren/api-testing/pkg/testing/remote" "github.com/linuxsuren/api-testing/pkg/testing/remote"
"github.com/linuxsuren/api-testing/pkg/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"google.golang.org/grpc" "google.golang.org/grpc"
"net"
"os"
) )
// Extension is the default command option of the extension // Extension is the default command option of the extension
@ -82,7 +84,7 @@ func CreateRunner(ext *Extension, c *cobra.Command, removeServer remote.LoaderSe
gRPCServer := grpc.NewServer() gRPCServer := grpc.NewServer()
remote.RegisterLoaderServer(gRPCServer, removeServer) remote.RegisterLoaderServer(gRPCServer, removeServer)
c.Printf("%s is running at %s\n", ext.GetFullName(), address) c.Printf("%s@%s is running at %s\n", ext.GetFullName(), version.GetVersion(), address)
RegisterStopSignal(c.Context(), func() { RegisterStopSignal(c.Context(), func() {
_ = os.Remove(ext.Socket) _ = os.Remove(ext.Socket)

View File

@ -2,7 +2,7 @@ package extension
import ( import (
"context" "context"
"fmt" "log"
"os" "os"
"os/signal" "os/signal"
) )
@ -19,11 +19,11 @@ func RegisterStopSignal(ctx context.Context, callback func(), servers ...StopAbl
case <-endChan: case <-endChan:
case <-ctx.Done(): case <-ctx.Done():
} }
fmt.Println("Stopping the server...")
if callback != nil { if callback != nil {
callback() callback()
} }
for _, server := range servers { for _, server := range servers {
log.Println("Stopping the server...")
server.Stop() server.Stop()
} }
}(ctx) }(ctx)

View File

@ -29,7 +29,7 @@ type fakeClient struct {
err error err error
} }
func (f*fakeClient)Invoke(ctx context.Context, reqBody interface{}, rspBody interface{}, opt ...client.Option) (err error) { func (f *fakeClient) Invoke(ctx context.Context, reqBody interface{}, rspBody interface{}, opt ...client.Option) (err error) {
err = f.err err = f.err
return return
} }

View File

@ -18,7 +18,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
package grpc_test package grpc_test
import ( import (

View File

@ -2,12 +2,12 @@ package runner
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"github.com/linuxsuren/api-testing/pkg/apispec"
"io"
"github.com/signintech/gopdf"
"github.com/flopp/go-findfont" "github.com/flopp/go-findfont"
"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/signintech/gopdf"
"io"
"log" "log"
"strconv" "strconv"
) )
@ -24,7 +24,6 @@ func NewPDFResultWriter(writer io.Writer) ReportResultWriter {
// Output writes the PDF base report to target writer // Output writes the PDF base report to target writer
func (w *pdfResultWriter) Output(result []ReportResult) (err error) { func (w *pdfResultWriter) Output(result []ReportResult) (err error) {
pdf := gopdf.GoPdf{} pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
fmt.Println(findfont.List()[len(findfont.List())-1]) fmt.Println(findfont.List()[len(findfont.List())-1])
@ -85,7 +84,7 @@ func (w *pdfResultWriter) Output(result []ReportResult) (err error) {
} }
} }
fmt.Fprint(w.writer, "Report is OK!") fmt.Fprint(w.writer, "Report is OK!")
pdf.WritePdf("Report.pdf") pdf.WritePdf("Report.pdf")
return return

View File

@ -44,6 +44,7 @@ func ToGRPCStore(store testing.Store) (result *Store) {
Url: store.URL, Url: store.URL,
Username: store.Username, Username: store.Username,
Password: store.Password, Password: store.Password,
Disabled: store.Disabled,
Properties: mapToPair(store.Properties), Properties: mapToPair(store.Properties),
} }
return return
@ -57,6 +58,7 @@ func ToNormalStore(store *Store) (result testing.Store) {
URL: store.Url, URL: store.Url,
Username: store.Username, Username: store.Username,
Password: store.Password, Password: store.Password,
Disabled: store.Disabled,
Properties: pairToMap(store.Properties), Properties: pairToMap(store.Properties),
} }
if store.Kind != nil { if store.Kind != nil {

View File

@ -42,6 +42,7 @@ func TestToGRPCStore(t *testing.T) {
Url: urlFoo, Url: urlFoo,
Username: "user", Username: "user",
Password: "pass", Password: "pass",
Disabled: true,
Properties: []*Pair{{ Properties: []*Pair{{
Key: "foo", Value: "bar", Key: "foo", Value: "bar",
}}, }},
@ -55,6 +56,7 @@ func TestToGRPCStore(t *testing.T) {
URL: urlFoo, URL: urlFoo,
Username: "user", Username: "user",
Password: "pass", Password: "pass",
Disabled: true,
Properties: map[string]string{ Properties: map[string]string{
"foo": "bar", "foo": "bar",
}, },

View File

@ -735,7 +735,8 @@ func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err e
grpcStore := ToGRPCStore(item) grpcStore := ToGRPCStore(item)
storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name}) storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name})
grpcStore.Ready = sErr == nil && storeStatus.Success grpcStore.Ready = sErr == nil && storeStatus.Ready
grpcStore.ReadOnly = storeStatus.ReadOnly
grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason
reply.Data = append(reply.Data, grpcStore) reply.Data = append(reply.Data, grpcStore)
@ -767,6 +768,7 @@ func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err
storeFactory := testing.NewStoreFactory(s.configDir) storeFactory := testing.NewStoreFactory(s.configDir)
store := ToNormalStore(in) store := ToNormalStore(in)
if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil { if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil {
// TODO need to restart extension if config was changed
err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
} }
return return
@ -777,13 +779,13 @@ func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err
err = storeFactory.DeleteStore(in.Name) err = storeFactory.DeleteStore(in.Name)
return return
} }
func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *CommonResult, err error) { func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *ExtensionStatus, err error) {
// TODO need to implement reply = &ExtensionStatus{}
reply = &CommonResult{}
var loader testing.Writer var loader testing.Writer
if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil { if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil {
verifyErr := loader.Verify() readOnly, verifyErr := loader.Verify()
reply.Success = verifyErr == nil reply.Ready = verifyErr == nil
reply.ReadOnly = readOnly
reply.Message = util.OKOrErrorMessage(verifyErr) reply.Message = util.OKOrErrorMessage(verifyErr)
} }
return return

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.28.1 // protoc-gen-go v1.28.1
// protoc v4.22.2 // protoc v4.25.0
// source: pkg/server/server.proto // source: pkg/server/server.proto
package server package server
@ -1585,6 +1585,8 @@ type Store struct {
Properties []*Pair `protobuf:"bytes,6,rep,name=properties,proto3" json:"properties,omitempty"` Properties []*Pair `protobuf:"bytes,6,rep,name=properties,proto3" json:"properties,omitempty"`
Kind *StoreKind `protobuf:"bytes,7,opt,name=kind,proto3" json:"kind,omitempty"` Kind *StoreKind `protobuf:"bytes,7,opt,name=kind,proto3" json:"kind,omitempty"`
Ready bool `protobuf:"varint,8,opt,name=ready,proto3" json:"ready,omitempty"` Ready bool `protobuf:"varint,8,opt,name=ready,proto3" json:"ready,omitempty"`
ReadOnly bool `protobuf:"varint,9,opt,name=readOnly,proto3" json:"readOnly,omitempty"`
Disabled bool `protobuf:"varint,10,opt,name=disabled,proto3" json:"disabled,omitempty"`
} }
func (x *Store) Reset() { func (x *Store) Reset() {
@ -1675,6 +1677,20 @@ func (x *Store) GetReady() bool {
return false return false
} }
func (x *Store) GetReadOnly() bool {
if x != nil {
return x.ReadOnly
}
return false
}
func (x *Store) GetDisabled() bool {
if x != nil {
return x.Disabled
}
return false
}
type StoreKinds struct { type StoreKinds struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -2107,6 +2123,77 @@ func (x *Secret) GetDescription() string {
return "" return ""
} }
type ExtensionStatus struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ready bool `protobuf:"varint,1,opt,name=ready,proto3" json:"ready,omitempty"`
ReadOnly bool `protobuf:"varint,2,opt,name=readOnly,proto3" json:"readOnly,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *ExtensionStatus) Reset() {
*x = ExtensionStatus{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_server_server_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ExtensionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExtensionStatus) ProtoMessage() {}
func (x *ExtensionStatus) ProtoReflect() protoreflect.Message {
mi := &file_pkg_server_server_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ExtensionStatus.ProtoReflect.Descriptor instead.
func (*ExtensionStatus) Descriptor() ([]byte, []int) {
return file_pkg_server_server_proto_rawDescGZIP(), []int{33}
}
func (x *ExtensionStatus) GetReady() bool {
if x != nil {
return x.Ready
}
return false
}
func (x *ExtensionStatus) GetReadOnly() bool {
if x != nil {
return x.ReadOnly
}
return false
}
func (x *ExtensionStatus) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *ExtensionStatus) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type Empty struct { type Empty struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -2116,7 +2203,7 @@ type Empty struct {
func (x *Empty) Reset() { func (x *Empty) Reset() {
*x = Empty{} *x = Empty{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_pkg_server_server_proto_msgTypes[33] mi := &file_pkg_server_server_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -2129,7 +2216,7 @@ func (x *Empty) String() string {
func (*Empty) ProtoMessage() {} func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message { func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_pkg_server_server_proto_msgTypes[33] mi := &file_pkg_server_server_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -2142,7 +2229,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message {
// Deprecated: Use Empty.ProtoReflect.Descriptor instead. // Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) { func (*Empty) Descriptor() ([]byte, []int) {
return file_pkg_server_server_proto_rawDescGZIP(), []int{33} return file_pkg_server_server_proto_rawDescGZIP(), []int{34}
} }
var File_pkg_server_server_proto protoreflect.FileDescriptor var File_pkg_server_server_proto protoreflect.FileDescriptor
@ -2316,7 +2403,7 @@ var file_pkg_server_server_proto_rawDesc = []byte{
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22,
0x2b, 0x0a, 0x06, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x2b, 0x0a, 0x06, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x04, 0x64, 0x61, 0x74,
0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf2, 0x01, 0x0a, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xaa, 0x02, 0x0a,
0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
@ -2332,164 +2419,175 @@ var file_pkg_server_server_proto_rawDesc = []byte{
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x65, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x72,
0x65, 0x61, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x61, 0x64, 0x65, 0x61, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x61, 0x64,
0x79, 0x22, 0x33, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x12, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20,
0x25, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1a, 0x0a,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52,
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4b, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x33, 0x0a, 0x0a, 0x53, 0x74, 0x6f,
0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4b,
0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x6c, 0x65, 0x64, 0x22, 0x42, 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72,
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x42, 0x0a, 0x0c, 0x43,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2e, 0x0a, 0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73,
0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75,
0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x69, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x72, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x2e, 0x0a, 0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x0a,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6d, 0x0a, 0x13, 0x43, 0x6f, 0x64, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x73, 0x65,
0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
0x12, 0x1c, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x20, 0x0a, 0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
0x01, 0x28, 0x09, 0x52, 0x09, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x1a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x65, 0x22, 0x6d, 0x0a, 0x13, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
0x52, 0x08, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x47, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74,
0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x47, 0x53, 0x75, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x54, 0x65, 0x73,
0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x2d, 0x0a, 0x07, 0x53, 0x65, 0x63, 0x72, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61,
0x65, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61,
0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18,
0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x54, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x22, 0x2d, 0x0a, 0x07, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x04, 0x64,
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x54, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d,
0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x86, 0x0f, 0x0a, 0x06, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61,
0x72, 0x12, 0x2d, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x6c, 0x75, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x77, 0x0a, 0x0f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
0x12, 0x2c, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x12, 0x0d, 0x2e, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x61, 0x64,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x73, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x61, 0x64, 0x79, 0x12, 0x1a,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x52, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x07,
0x22, 0x00, 0x12, 0x42, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73, 0x74, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x89, 0x0f, 0x0a, 0x06, 0x52, 0x75, 0x6e, 0x6e,
0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x14, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x1a, 0x12, 0x2e, 0x73, 0x65,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,
0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x00, 0x12, 0x2c, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x12, 0x0d,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x00, 0x12,
0x42, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69,
0x74, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74,
0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x12, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x65, 0x73,
0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a,
0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x65,
0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x1a, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74,
0x53, 0x75, 0x69, 0x74, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x11, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73,
0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x1a, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x75, 0x69, 0x74, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x54,
0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e, 0x73, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x69, 0x74,
0x22, 0x00, 0x12, 0x42, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73,
0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x74, 0x65, 0x64, 0x41, 0x50, 0x49, 0x73, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74,
0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x43, 0x61, 0x73, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x54, 0x65,
0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x16, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61,
0x22, 0x00, 0x12, 0x42, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x73, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0b, 0x47, 0x65,
0x65, 0x64, 0x41, 0x50, 0x49, 0x73, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x74, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74,
0x79, 0x1a, 0x11, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x69, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73,
0x61, 0x73, 0x65, 0x73, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x57, 0x69, 0x74, 0x68, 0x53,
0x16, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65,
0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x55, 0x70,
0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73,
0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x57, 0x69,
0x74, 0x79, 0x1a, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x74, 0x68, 0x53, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x43, 0x61, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a,
0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12,
0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x57, 0x69, 0x74, 0x68, 0x53, 0x75, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73,
0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12,
0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x65, 0x38, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x57, 0x69, 0x74, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d,
0x68, 0x53, 0x75, 0x69, 0x74, 0x65, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d,
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0e, 0x70, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0c, 0x47, 0x65, 0x6e,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x18, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52,
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x72, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x38, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x34,
0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x72, 0x12,
0x74, 0x6f, 0x72, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12,
0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4c, 0x69,
0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x74, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x54,
0x72, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x30, 0x0a,
0x0d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x72, 0x12, 0x0d, 0x0e, 0x50, 0x6f, 0x70, 0x75, 0x6c, 0x61, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x00, 0x12,
0x74, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x54, 0x65, 0x36, 0x0a, 0x0e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x51, 0x75, 0x65, 0x72,
0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c,
0x2e, 0x43, 0x6f, 0x64, 0x65, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x63, 0x74,
0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x0e, 0x69, 0x6f, 0x6e, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
0x50, 0x6f, 0x70, 0x75, 0x6c, 0x61, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x0d, 0x13, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x51,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x00, 0x12, 0x36, 0x69, 0x72, 0x73, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74,
0x0a, 0x0e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x12, 0x13, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x06,
0x61, 0x69, 0x72, 0x73, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x6f, 0x6e, 0x73, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x13,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x51, 0x75,
0x65, 0x72, 0x79, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x69,
0x72, 0x73, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48,
0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x06, 0x53, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x0d, 0x47,
0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x12, 0x0d, 0x2e, 0x73,
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65,
0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x0d, 0x47, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x22,
0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x00, 0x12, 0x2c, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x0d,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e,
0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x73, 0x22, 0x00, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x22, 0x00, 0x12,
0x12, 0x2c, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x0d, 0x2e, 0x2d, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x22, 0x00, 0x12, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x2d,
0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d, 0x2e, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a,
0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d, 0x2e, 0x73, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x0b, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0b,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x13, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79,
0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x56, 0x1a, 0x17, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x13, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x0a, 0x47,
0x76, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x43,
0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65,
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65,
0x63, 0x72, 0x65, 0x74, 0x73, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63,
0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63,
0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d,
0x36, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x55,
0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65,
0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x74, 0x22, 0x00, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x73, 0x75, 0x72, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69,
0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x42, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72,
0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x75, 0x78, 0x73, 0x75, 0x72, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x74, 0x65, 0x73,
0x74, 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -2504,7 +2602,7 @@ func file_pkg_server_server_proto_rawDescGZIP() []byte {
return file_pkg_server_server_proto_rawDescData return file_pkg_server_server_proto_rawDescData
} }
var file_pkg_server_server_proto_msgTypes = make([]protoimpl.MessageInfo, 36) var file_pkg_server_server_proto_msgTypes = make([]protoimpl.MessageInfo, 37)
var file_pkg_server_server_proto_goTypes = []interface{}{ var file_pkg_server_server_proto_goTypes = []interface{}{
(*Suites)(nil), // 0: server.Suites (*Suites)(nil), // 0: server.Suites
(*Items)(nil), // 1: server.Items (*Items)(nil), // 1: server.Items
@ -2539,18 +2637,19 @@ var file_pkg_server_server_proto_goTypes = []interface{}{
(*CodeGenerateRequest)(nil), // 30: server.CodeGenerateRequest (*CodeGenerateRequest)(nil), // 30: server.CodeGenerateRequest
(*Secrets)(nil), // 31: server.Secrets (*Secrets)(nil), // 31: server.Secrets
(*Secret)(nil), // 32: server.Secret (*Secret)(nil), // 32: server.Secret
(*Empty)(nil), // 33: server.Empty (*ExtensionStatus)(nil), // 33: server.ExtensionStatus
nil, // 34: server.Suites.DataEntry (*Empty)(nil), // 34: server.Empty
nil, // 35: server.TestTask.EnvEntry nil, // 35: server.Suites.DataEntry
nil, // 36: server.TestTask.EnvEntry
} }
var file_pkg_server_server_proto_depIdxs = []int32{ var file_pkg_server_server_proto_depIdxs = []int32{
34, // 0: server.Suites.data:type_name -> server.Suites.DataEntry 35, // 0: server.Suites.data:type_name -> server.Suites.DataEntry
20, // 1: server.TestCaseIdentity.parameters:type_name -> server.Pair 20, // 1: server.TestCaseIdentity.parameters:type_name -> server.Pair
20, // 2: server.TestSuite.param:type_name -> server.Pair 20, // 2: server.TestSuite.param:type_name -> server.Pair
5, // 3: server.TestSuite.spec:type_name -> server.APISpec 5, // 3: server.TestSuite.spec:type_name -> server.APISpec
7, // 4: server.APISpec.rpc:type_name -> server.RPC 7, // 4: server.APISpec.rpc:type_name -> server.RPC
6, // 5: server.APISpec.secure:type_name -> server.Secure 6, // 5: server.APISpec.secure:type_name -> server.Secure
35, // 6: server.TestTask.env:type_name -> server.TestTask.EnvEntry 36, // 6: server.TestTask.env:type_name -> server.TestTask.EnvEntry
20, // 7: server.TestTask.parameters:type_name -> server.Pair 20, // 7: server.TestTask.parameters:type_name -> server.Pair
19, // 8: server.TestResult.testCaseResult:type_name -> server.TestCaseResult 19, // 8: server.TestResult.testCaseResult:type_name -> server.TestCaseResult
15, // 9: server.Suite.items:type_name -> server.TestCase 15, // 9: server.Suite.items:type_name -> server.TestCase
@ -2574,7 +2673,7 @@ var file_pkg_server_server_proto_depIdxs = []int32{
32, // 27: server.Secrets.data:type_name -> server.Secret 32, // 27: server.Secrets.data:type_name -> server.Secret
1, // 28: server.Suites.DataEntry.value:type_name -> server.Items 1, // 28: server.Suites.DataEntry.value:type_name -> server.Items
9, // 29: server.Runner.Run:input_type -> server.TestTask 9, // 29: server.Runner.Run:input_type -> server.TestTask
33, // 30: server.Runner.GetSuites:input_type -> server.Empty 34, // 30: server.Runner.GetSuites:input_type -> server.Empty
8, // 31: server.Runner.CreateTestSuite:input_type -> server.TestSuiteIdentity 8, // 31: server.Runner.CreateTestSuite:input_type -> server.TestSuiteIdentity
3, // 32: server.Runner.ImportTestSuite:input_type -> server.TestSuiteSource 3, // 32: server.Runner.ImportTestSuite:input_type -> server.TestSuiteSource
8, // 33: server.Runner.GetTestSuite:input_type -> server.TestSuiteIdentity 8, // 33: server.Runner.GetTestSuite:input_type -> server.TestSuiteIdentity
@ -2587,22 +2686,22 @@ var file_pkg_server_server_proto_depIdxs = []int32{
13, // 40: server.Runner.CreateTestCase:input_type -> server.TestCaseWithSuite 13, // 40: server.Runner.CreateTestCase:input_type -> server.TestCaseWithSuite
13, // 41: server.Runner.UpdateTestCase:input_type -> server.TestCaseWithSuite 13, // 41: server.Runner.UpdateTestCase:input_type -> server.TestCaseWithSuite
2, // 42: server.Runner.DeleteTestCase:input_type -> server.TestCaseIdentity 2, // 42: server.Runner.DeleteTestCase:input_type -> server.TestCaseIdentity
33, // 43: server.Runner.ListCodeGenerator:input_type -> server.Empty 34, // 43: server.Runner.ListCodeGenerator:input_type -> server.Empty
30, // 44: server.Runner.GenerateCode:input_type -> server.CodeGenerateRequest 30, // 44: server.Runner.GenerateCode:input_type -> server.CodeGenerateRequest
33, // 45: server.Runner.ListConverter:input_type -> server.Empty 34, // 45: server.Runner.ListConverter:input_type -> server.Empty
30, // 46: server.Runner.ConvertTestSuite:input_type -> server.CodeGenerateRequest 30, // 46: server.Runner.ConvertTestSuite:input_type -> server.CodeGenerateRequest
33, // 47: server.Runner.PopularHeaders:input_type -> server.Empty 34, // 47: server.Runner.PopularHeaders:input_type -> server.Empty
22, // 48: server.Runner.FunctionsQuery:input_type -> server.SimpleQuery 22, // 48: server.Runner.FunctionsQuery:input_type -> server.SimpleQuery
22, // 49: server.Runner.FunctionsQueryStream:input_type -> server.SimpleQuery 22, // 49: server.Runner.FunctionsQueryStream:input_type -> server.SimpleQuery
33, // 50: server.Runner.GetVersion:input_type -> server.Empty 34, // 50: server.Runner.GetVersion:input_type -> server.Empty
33, // 51: server.Runner.Sample:input_type -> server.Empty 34, // 51: server.Runner.Sample:input_type -> server.Empty
33, // 52: server.Runner.GetStoreKinds:input_type -> server.Empty 34, // 52: server.Runner.GetStoreKinds:input_type -> server.Empty
33, // 53: server.Runner.GetStores:input_type -> server.Empty 34, // 53: server.Runner.GetStores:input_type -> server.Empty
24, // 54: server.Runner.CreateStore:input_type -> server.Store 24, // 54: server.Runner.CreateStore:input_type -> server.Store
24, // 55: server.Runner.UpdateStore:input_type -> server.Store 24, // 55: server.Runner.UpdateStore:input_type -> server.Store
24, // 56: server.Runner.DeleteStore:input_type -> server.Store 24, // 56: server.Runner.DeleteStore:input_type -> server.Store
22, // 57: server.Runner.VerifyStore:input_type -> server.SimpleQuery 22, // 57: server.Runner.VerifyStore:input_type -> server.SimpleQuery
33, // 58: server.Runner.GetSecrets:input_type -> server.Empty 34, // 58: server.Runner.GetSecrets:input_type -> server.Empty
32, // 59: server.Runner.CreateSecret:input_type -> server.Secret 32, // 59: server.Runner.CreateSecret:input_type -> server.Secret
32, // 60: server.Runner.DeleteSecret:input_type -> server.Secret 32, // 60: server.Runner.DeleteSecret:input_type -> server.Secret
32, // 61: server.Runner.UpdateSecret:input_type -> server.Secret 32, // 61: server.Runner.UpdateSecret:input_type -> server.Secret
@ -2634,7 +2733,7 @@ var file_pkg_server_server_proto_depIdxs = []int32{
24, // 87: server.Runner.CreateStore:output_type -> server.Store 24, // 87: server.Runner.CreateStore:output_type -> server.Store
24, // 88: server.Runner.UpdateStore:output_type -> server.Store 24, // 88: server.Runner.UpdateStore:output_type -> server.Store
24, // 89: server.Runner.DeleteStore:output_type -> server.Store 24, // 89: server.Runner.DeleteStore:output_type -> server.Store
27, // 90: server.Runner.VerifyStore:output_type -> server.CommonResult 33, // 90: server.Runner.VerifyStore:output_type -> server.ExtensionStatus
31, // 91: server.Runner.GetSecrets:output_type -> server.Secrets 31, // 91: server.Runner.GetSecrets:output_type -> server.Secrets
27, // 92: server.Runner.CreateSecret:output_type -> server.CommonResult 27, // 92: server.Runner.CreateSecret:output_type -> server.CommonResult
27, // 93: server.Runner.DeleteSecret:output_type -> server.CommonResult 27, // 93: server.Runner.DeleteSecret:output_type -> server.CommonResult
@ -3049,6 +3148,18 @@ func file_pkg_server_server_proto_init() {
} }
} }
file_pkg_server_server_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { file_pkg_server_server_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ExtensionStatus); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_server_server_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i { switch v := v.(*Empty); i {
case 0: case 0:
return &v.state return &v.state
@ -3067,7 +3178,7 @@ func file_pkg_server_server_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_server_server_proto_rawDesc, RawDescriptor: file_pkg_server_server_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 36, NumMessages: 37,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -45,7 +45,7 @@ service Runner {
rpc CreateStore(Store) returns (Store) {} rpc CreateStore(Store) returns (Store) {}
rpc UpdateStore(Store) returns (Store) {} rpc UpdateStore(Store) returns (Store) {}
rpc DeleteStore(Store) returns (Store) {} rpc DeleteStore(Store) returns (Store) {}
rpc VerifyStore(SimpleQuery) returns (CommonResult) {} rpc VerifyStore(SimpleQuery) returns (ExtensionStatus) {}
// secret related interfaces // secret related interfaces
rpc GetSecrets(Empty) returns (Secrets) {} rpc GetSecrets(Empty) returns (Secrets) {}
@ -212,6 +212,8 @@ message Store {
repeated Pair properties = 6; repeated Pair properties = 6;
StoreKind kind = 7; StoreKind kind = 7;
bool ready = 8; bool ready = 8;
bool readOnly = 9;
bool disabled = 10;
} }
message StoreKinds { message StoreKinds {
@ -253,5 +255,12 @@ message Secret {
string Description = 3; string Description = 3;
} }
message ExtensionStatus {
bool ready = 1;
bool readOnly = 2;
string version = 3;
string message = 4;
}
message Empty { message Empty {
} }

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.2.0 // - protoc-gen-go-grpc v1.2.0
// - protoc v4.22.2 // - protoc v4.25.0
// source: pkg/server/server.proto // source: pkg/server/server.proto
package server package server
@ -56,7 +56,7 @@ type RunnerClient interface {
CreateStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error) CreateStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error)
UpdateStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error) UpdateStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error)
DeleteStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error) DeleteStore(ctx context.Context, in *Store, opts ...grpc.CallOption) (*Store, error)
VerifyStore(ctx context.Context, in *SimpleQuery, opts ...grpc.CallOption) (*CommonResult, error) VerifyStore(ctx context.Context, in *SimpleQuery, opts ...grpc.CallOption) (*ExtensionStatus, error)
// secret related interfaces // secret related interfaces
GetSecrets(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Secrets, error) GetSecrets(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Secrets, error)
CreateSecret(ctx context.Context, in *Secret, opts ...grpc.CallOption) (*CommonResult, error) CreateSecret(ctx context.Context, in *Secret, opts ...grpc.CallOption) (*CommonResult, error)
@ -346,8 +346,8 @@ func (c *runnerClient) DeleteStore(ctx context.Context, in *Store, opts ...grpc.
return out, nil return out, nil
} }
func (c *runnerClient) VerifyStore(ctx context.Context, in *SimpleQuery, opts ...grpc.CallOption) (*CommonResult, error) { func (c *runnerClient) VerifyStore(ctx context.Context, in *SimpleQuery, opts ...grpc.CallOption) (*ExtensionStatus, error) {
out := new(CommonResult) out := new(ExtensionStatus)
err := c.cc.Invoke(ctx, "/server.Runner/VerifyStore", in, out, opts...) err := c.cc.Invoke(ctx, "/server.Runner/VerifyStore", in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -429,7 +429,7 @@ type RunnerServer interface {
CreateStore(context.Context, *Store) (*Store, error) CreateStore(context.Context, *Store) (*Store, error)
UpdateStore(context.Context, *Store) (*Store, error) UpdateStore(context.Context, *Store) (*Store, error)
DeleteStore(context.Context, *Store) (*Store, error) DeleteStore(context.Context, *Store) (*Store, error)
VerifyStore(context.Context, *SimpleQuery) (*CommonResult, error) VerifyStore(context.Context, *SimpleQuery) (*ExtensionStatus, error)
// secret related interfaces // secret related interfaces
GetSecrets(context.Context, *Empty) (*Secrets, error) GetSecrets(context.Context, *Empty) (*Secrets, error)
CreateSecret(context.Context, *Secret) (*CommonResult, error) CreateSecret(context.Context, *Secret) (*CommonResult, error)
@ -526,7 +526,7 @@ func (UnimplementedRunnerServer) UpdateStore(context.Context, *Store) (*Store, e
func (UnimplementedRunnerServer) DeleteStore(context.Context, *Store) (*Store, error) { func (UnimplementedRunnerServer) DeleteStore(context.Context, *Store) (*Store, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteStore not implemented") return nil, status.Errorf(codes.Unimplemented, "method DeleteStore not implemented")
} }
func (UnimplementedRunnerServer) VerifyStore(context.Context, *SimpleQuery) (*CommonResult, error) { func (UnimplementedRunnerServer) VerifyStore(context.Context, *SimpleQuery) (*ExtensionStatus, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyStore not implemented") return nil, status.Errorf(codes.Unimplemented, "method VerifyStore not implemented")
} }
func (UnimplementedRunnerServer) GetSecrets(context.Context, *Empty) (*Secrets, error) { func (UnimplementedRunnerServer) GetSecrets(context.Context, *Empty) (*Secrets, error) {

View File

@ -71,7 +71,7 @@ func (s *storeExtManager) Start(name, socket string) (err error) {
socketFile := strings.TrimPrefix(socketURL, s.socketPrefix) socketFile := strings.TrimPrefix(socketURL, s.socketPrefix)
s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile) s.filesNeedToBeRemoved = append(s.filesNeedToBeRemoved, socketFile)
s.extStatusMap[name] = true s.extStatusMap[name] = true
if err = s.execer.RunCommand(plugin, "--socket", socketFile); err != nil { if err = s.execer.RunCommandWithIO(plugin, "", os.Stdout, os.Stderr, "--socket", socketFile); err != nil {
log.Printf("failed to start %s, error: %v", socketURL, err) log.Printf("failed to start %s, error: %v", socketURL, err)
} }
}(socket, binaryPath) }(socket, binaryPath)

View File

@ -9,7 +9,7 @@ type Loader interface {
GetCount() int GetCount() int
Reset() Reset()
Verify() (err error) Verify() (readOnly bool, err error)
} }
type Writer interface { type Writer interface {

View File

@ -380,7 +380,7 @@ func (l *fileLoader) DeleteTestCase(suiteName, testcase string) (err error) {
return return
} }
func (l *fileLoader) Verify() (err error) { func (l *fileLoader) Verify() (readOnly bool, err error) {
// always be okay // always be okay
return return
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/h2non/gock" "github.com/h2non/gock"
atest "github.com/linuxsuren/api-testing/pkg/testing" atest "github.com/linuxsuren/api-testing/pkg/testing"
atesting "github.com/linuxsuren/api-testing/pkg/testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -40,7 +39,9 @@ func TestFileLoader(t *testing.T) {
} }
tt.verify(t, loader) tt.verify(t, loader)
assert.NoError(t, loader.Verify()) readonly, err := loader.Verify()
assert.NoError(t, err)
assert.False(t, readonly)
}) })
} }
} }
@ -186,7 +187,7 @@ func TestSuite(t *testing.T) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
var testcase atesting.TestCase var testcase atest.TestCase
testcase, err = writer.GetTestCase("test", "login") testcase, err = writer.GetTestCase("test", "login")
if assert.NoError(t, err) { if assert.NoError(t, err) {
assert.Equal(t, urlTestLogin, testcase.Request.API) assert.Equal(t, urlTestLogin, testcase.Request.API)

View File

@ -27,6 +27,7 @@ package remote
import ( import (
context "context" context "context"
"errors" "errors"
"time"
server "github.com/linuxsuren/api-testing/pkg/server" server "github.com/linuxsuren/api-testing/pkg/server"
"github.com/linuxsuren/api-testing/pkg/testing" "github.com/linuxsuren/api-testing/pkg/testing"
@ -187,10 +188,15 @@ func (g *gRPCLoader) DeleteSuite(name string) (err error) {
return return
} }
func (g *gRPCLoader) Verify() (err error) { func (g *gRPCLoader) Verify() (readOnly bool, err error) {
var result *server.CommonResult // avoid to long to wait the response
if result, err = g.client.Verify(g.ctx, &server.Empty{}); err == nil { ctx, cancel := context.WithTimeout(g.ctx, time.Second*3)
if !result.Success { defer cancel()
var result *server.ExtensionStatus
if result, err = g.client.Verify(ctx, &server.Empty{}); err == nil {
readOnly = result.ReadOnly
if !result.Ready {
err = errors.New(result.Message) err = errors.New(result.Message)
} }
} }

View File

@ -95,8 +95,10 @@ func TestNewGRPCLoader(t *testing.T) {
err = writer.DeleteSuite("") err = writer.DeleteSuite("")
assert.Error(t, err) assert.Error(t, err)
err = writer.Verify() var readonly bool
readonly, err = writer.Verify()
assert.Error(t, err) assert.Error(t, err)
assert.False(t, readonly)
}) })
t.Run("NewGRPCloaderFromStore", func(t *testing.T) { t.Run("NewGRPCloaderFromStore", func(t *testing.T) {

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.28.1 // protoc-gen-go v1.28.1
// protoc v4.22.2 // protoc v4.25.0
// source: pkg/testing/remote/loader.proto // source: pkg/testing/remote/loader.proto
package remote package remote
@ -177,7 +177,7 @@ var file_pkg_testing_remote_loader_proto_rawDesc = []byte{
0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73,
0x74, 0x43, 0x61, 0x73, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x43, 0x61, 0x73, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x12, 0x0a, 0x04,
0x66, 0x75, 0x6c, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x75, 0x6c, 0x6c, 0x66, 0x75, 0x6c, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x66, 0x75, 0x6c, 0x6c,
0x32, 0xe0, 0x04, 0x0a, 0x06, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x4c, 0x32, 0xe3, 0x04, 0x0a, 0x06, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x4c,
0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x73, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x72, 0x65, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x22,
@ -212,31 +212,31 @@ var file_pkg_testing_remote_loader_proto_rawDesc = []byte{
0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x65, 0x12,
0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x61, 0x73,
0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x65, 0x1a, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x0d, 0x2e, 0x73, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x06, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x0d, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74,
0x74, 0x22, 0x00, 0x32, 0x96, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x53, 0x65, 0x61, 0x74, 0x75, 0x73, 0x22, 0x00, 0x32, 0x96, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53,
0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53,
0x65, 0x74, 0x1a, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53,
0x65, 0x74, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65,
0x74, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45,
0x79, 0x1a, 0x0f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65,
0x74, 0x73, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74,
0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x36, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52,
0x6c, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74,
0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x42,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69,
0x73, 0x75, 0x72, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x6e, 0x75, 0x78, 0x73, 0x75, 0x72, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x74, 0x65, 0x73,
0x67, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67,
0x6d, 0x6f, 0x74, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -253,16 +253,17 @@ func file_pkg_testing_remote_loader_proto_rawDescGZIP() []byte {
var file_pkg_testing_remote_loader_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_pkg_testing_remote_loader_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_pkg_testing_remote_loader_proto_goTypes = []interface{}{ var file_pkg_testing_remote_loader_proto_goTypes = []interface{}{
(*TestSuites)(nil), // 0: remote.TestSuites (*TestSuites)(nil), // 0: remote.TestSuites
(*TestSuite)(nil), // 1: remote.TestSuite (*TestSuite)(nil), // 1: remote.TestSuite
(*server.Pair)(nil), // 2: server.Pair (*server.Pair)(nil), // 2: server.Pair
(*server.APISpec)(nil), // 3: server.APISpec (*server.APISpec)(nil), // 3: server.APISpec
(*server.TestCase)(nil), // 4: server.TestCase (*server.TestCase)(nil), // 4: server.TestCase
(*server.Empty)(nil), // 5: server.Empty (*server.Empty)(nil), // 5: server.Empty
(*server.Secret)(nil), // 6: server.Secret (*server.Secret)(nil), // 6: server.Secret
(*server.TestCases)(nil), // 7: server.TestCases (*server.TestCases)(nil), // 7: server.TestCases
(*server.CommonResult)(nil), // 8: server.CommonResult (*server.ExtensionStatus)(nil), // 8: server.ExtensionStatus
(*server.Secrets)(nil), // 9: server.Secrets (*server.Secrets)(nil), // 9: server.Secrets
(*server.CommonResult)(nil), // 10: server.CommonResult
} }
var file_pkg_testing_remote_loader_proto_depIdxs = []int32{ var file_pkg_testing_remote_loader_proto_depIdxs = []int32{
1, // 0: remote.TestSuites.data:type_name -> remote.TestSuite 1, // 0: remote.TestSuites.data:type_name -> remote.TestSuite
@ -295,12 +296,12 @@ var file_pkg_testing_remote_loader_proto_depIdxs = []int32{
4, // 27: remote.Loader.GetTestCase:output_type -> server.TestCase 4, // 27: remote.Loader.GetTestCase:output_type -> server.TestCase
4, // 28: remote.Loader.UpdateTestCase:output_type -> server.TestCase 4, // 28: remote.Loader.UpdateTestCase:output_type -> server.TestCase
5, // 29: remote.Loader.DeleteTestCase:output_type -> server.Empty 5, // 29: remote.Loader.DeleteTestCase:output_type -> server.Empty
8, // 30: remote.Loader.Verify:output_type -> server.CommonResult 8, // 30: remote.Loader.Verify:output_type -> server.ExtensionStatus
6, // 31: remote.SecretService.GetSecret:output_type -> server.Secret 6, // 31: remote.SecretService.GetSecret:output_type -> server.Secret
9, // 32: remote.SecretService.GetSecrets:output_type -> server.Secrets 9, // 32: remote.SecretService.GetSecrets:output_type -> server.Secrets
8, // 33: remote.SecretService.CreateSecret:output_type -> server.CommonResult 10, // 33: remote.SecretService.CreateSecret:output_type -> server.CommonResult
8, // 34: remote.SecretService.DeleteSecret:output_type -> server.CommonResult 10, // 34: remote.SecretService.DeleteSecret:output_type -> server.CommonResult
8, // 35: remote.SecretService.UpdateSecret:output_type -> server.CommonResult 10, // 35: remote.SecretService.UpdateSecret:output_type -> server.CommonResult
20, // [20:36] is the sub-list for method output_type 20, // [20:36] is the sub-list for method output_type
4, // [4:20] is the sub-list for method input_type 4, // [4:20] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name 4, // [4:4] is the sub-list for extension type_name

View File

@ -19,7 +19,7 @@ service Loader {
rpc UpdateTestCase(server.TestCase) returns (server.TestCase) {} rpc UpdateTestCase(server.TestCase) returns (server.TestCase) {}
rpc DeleteTestCase(server.TestCase) returns (server.Empty) {} rpc DeleteTestCase(server.TestCase) returns (server.Empty) {}
rpc Verify(server.Empty) returns (server.CommonResult) {} rpc Verify(server.Empty) returns (server.ExtensionStatus) {}
} }
message TestSuites { message TestSuites {

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.2.0 // - protoc-gen-go-grpc v1.2.0
// - protoc v4.22.2 // - protoc v4.25.0
// source: pkg/testing/remote/loader.proto // source: pkg/testing/remote/loader.proto
package remote package remote
@ -33,7 +33,7 @@ type LoaderClient interface {
GetTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.TestCase, error) GetTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.TestCase, error)
UpdateTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.TestCase, error) UpdateTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.TestCase, error)
DeleteTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.Empty, error) DeleteTestCase(ctx context.Context, in *server.TestCase, opts ...grpc.CallOption) (*server.Empty, error)
Verify(ctx context.Context, in *server.Empty, opts ...grpc.CallOption) (*server.CommonResult, error) Verify(ctx context.Context, in *server.Empty, opts ...grpc.CallOption) (*server.ExtensionStatus, error)
} }
type loaderClient struct { type loaderClient struct {
@ -134,8 +134,8 @@ func (c *loaderClient) DeleteTestCase(ctx context.Context, in *server.TestCase,
return out, nil return out, nil
} }
func (c *loaderClient) Verify(ctx context.Context, in *server.Empty, opts ...grpc.CallOption) (*server.CommonResult, error) { func (c *loaderClient) Verify(ctx context.Context, in *server.Empty, opts ...grpc.CallOption) (*server.ExtensionStatus, error) {
out := new(server.CommonResult) out := new(server.ExtensionStatus)
err := c.cc.Invoke(ctx, "/remote.Loader/Verify", in, out, opts...) err := c.cc.Invoke(ctx, "/remote.Loader/Verify", in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -157,7 +157,7 @@ type LoaderServer interface {
GetTestCase(context.Context, *server.TestCase) (*server.TestCase, error) GetTestCase(context.Context, *server.TestCase) (*server.TestCase, error)
UpdateTestCase(context.Context, *server.TestCase) (*server.TestCase, error) UpdateTestCase(context.Context, *server.TestCase) (*server.TestCase, error)
DeleteTestCase(context.Context, *server.TestCase) (*server.Empty, error) DeleteTestCase(context.Context, *server.TestCase) (*server.Empty, error)
Verify(context.Context, *server.Empty) (*server.CommonResult, error) Verify(context.Context, *server.Empty) (*server.ExtensionStatus, error)
mustEmbedUnimplementedLoaderServer() mustEmbedUnimplementedLoaderServer()
} }
@ -195,7 +195,7 @@ func (UnimplementedLoaderServer) UpdateTestCase(context.Context, *server.TestCas
func (UnimplementedLoaderServer) DeleteTestCase(context.Context, *server.TestCase) (*server.Empty, error) { func (UnimplementedLoaderServer) DeleteTestCase(context.Context, *server.TestCase) (*server.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteTestCase not implemented") return nil, status.Errorf(codes.Unimplemented, "method DeleteTestCase not implemented")
} }
func (UnimplementedLoaderServer) Verify(context.Context, *server.Empty) (*server.CommonResult, error) { func (UnimplementedLoaderServer) Verify(context.Context, *server.Empty) (*server.ExtensionStatus, error) {
return nil, status.Errorf(codes.Unimplemented, "method Verify not implemented") return nil, status.Errorf(codes.Unimplemented, "method Verify not implemented")
} }
func (UnimplementedLoaderServer) mustEmbedUnimplementedLoaderServer() {} func (UnimplementedLoaderServer) mustEmbedUnimplementedLoaderServer() {}

View File

@ -45,6 +45,8 @@ type Store struct {
URL string URL string
Username string Username string
Password string Password string
ReadOnly bool
Disabled bool
Properties map[string]string Properties map[string]string
} }
@ -57,6 +59,8 @@ func (s *Store) ToMap() (result map[string]string) {
"url": s.URL, "url": s.URL,
"username": s.Username, "username": s.Username,
"password": s.Password, "password": s.Password,
"readonly": fmt.Sprintf("%t", s.ReadOnly),
"disabled": fmt.Sprintf("%t", s.Disabled),
} }
for key, val := range s.Properties { for key, val := range s.Properties {
result["pro."+key] = val result["pro."+key] = val
@ -71,6 +75,8 @@ func MapToStore(data map[string]string) (store Store) {
URL: data["url"], URL: data["url"],
Username: data["username"], Username: data["username"],
Password: data["password"], Password: data["password"],
ReadOnly: data["readonly"] == "true",
Disabled: data["disabled"] == "true",
Kind: StoreKind{ Kind: StoreKind{
Name: data["kind"], Name: data["kind"],
URL: data["kind.url"], URL: data["kind.url"],

View File

@ -133,9 +133,11 @@ var sampleStoreMap = map[string]string{
"kind.url": fooURL, "kind.url": fooURL,
"kind": "test", "kind": "test",
"description": "desc", "description": "desc",
"disabled": "false",
"username": "user", "username": "user",
"password": "pass", "password": "pass",
"pro.key": "val", "pro.key": "val",
"readonly": "false",
} }
var sampleStore = &Store{ var sampleStore = &Store{

View File

@ -8,6 +8,8 @@ stores:
url: localhost:4000 url: localhost:4000
username: root username: root
password: "" password: ""
readonly: false
disabled: false
properties: properties:
database: test database: test
plugins: plugins: