mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-20 14:55:50 +00:00
example scenarios
This commit is contained in:
parent
0d9e882e54
commit
2163b7c7f8
44 changed files with 1177 additions and 411 deletions
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
FROM eclipse-temurin:17-jre
|
||||||
|
EXPOSE 8082
|
||||||
|
|
||||||
|
ENV OTEL_SERVICE_NAME=PetClinic
|
||||||
|
ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:5050
|
||||||
|
ENV OTEL_LOGS_EXPORTER="otlp"
|
||||||
|
ENV OTEL_METRICS_EXPORTER=none
|
||||||
|
|
||||||
|
ENV CODE_PACKAGE_PREFIXES="org.springframework.samples.petclinic"
|
||||||
|
ENV DEPLOYMENT_ENV="SAMPLE_ENV"
|
||||||
|
|
||||||
|
ADD target/spring-petclinic-*.jar /app.jar
|
||||||
|
ADD build/otel/opentelemetry-javaagent.jar /opentelemetry-javaagent.jar
|
||||||
|
ADD build/otel/digma-otel-agent-extension.jar /digma-otel-agent-extension.jar
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=20s --timeout=3s --start-period=10s --retries=4 \
|
||||||
|
CMD curl -f http://localhost:8082/ || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT java -jar -javaagent:/opentelemetry-javaagent.jar -Dotel.javaagent.extensions=/digma-otel-agent-extension.jar app.jar
|
BIN
digma-otel-agent-extension.jar
Normal file
BIN
digma-otel-agent-extension.jar
Normal file
Binary file not shown.
15
docker-compose.override.otel.yml
Normal file
15
docker-compose.override.otel.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
pet-clinic:
|
||||||
|
volumes:
|
||||||
|
- "./otel/opentelemetry-javaagent.jar:/otel/opentelemetry-javaagent.jar"
|
||||||
|
- "./otel/digma-otel-agent-extension.jar:/otel/digma-otel-agent-extension.jar"
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- JAVA_TOOL_OPTIONS=-javaagent:/otel/opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=http://host.docker.internal:5050 -Dotel.javaagent.extensions=/otel/digma-otel-agent-extension.jar
|
||||||
|
- OTEL_SERVICE_NAME=pet-clinic
|
||||||
|
- DEPLOYMENT_ENV=LOCAL_DOCKER
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
|
@ -1,6 +1,18 @@
|
||||||
version: "2.2"
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
pet-clinic:
|
||||||
|
build: ./
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "curl", "-f", "http://localhost:8082/" ]
|
||||||
|
interval: 20s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 4
|
||||||
|
start_period: 5s
|
||||||
|
ports:
|
||||||
|
- "8082:8082"
|
||||||
|
entrypoint: java -jar app.jar
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
ports:
|
ports:
|
||||||
|
|
BIN
opentelemetry-javaagent.jar
Normal file
BIN
opentelemetry-javaagent.jar
Normal file
Binary file not shown.
BIN
otel/digma-otel-agent-extension.jar
Normal file
BIN
otel/digma-otel-agent-extension.jar
Normal file
Binary file not shown.
BIN
otel/opentelemetry-javaagent.jar
Normal file
BIN
otel/opentelemetry-javaagent.jar
Normal file
Binary file not shown.
23
petshop-chart/.helmignore
Normal file
23
petshop-chart/.helmignore
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
24
petshop-chart/Chart.yaml
Normal file
24
petshop-chart/Chart.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: v2
|
||||||
|
name: petshop-chart
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.3
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "1.16.0"
|
22
petshop-chart/templates/NOTES.txt
Normal file
22
petshop-chart/templates/NOTES.txt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
1. Get the application URL by running these commands:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- range $host := .Values.ingress.hosts }}
|
||||||
|
{{- range .paths }}
|
||||||
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "petshop-chart.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "petshop-chart.fullname" . }}'
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "petshop-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "petshop-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||||
|
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||||
|
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||||
|
{{- end }}
|
62
petshop-chart/templates/_helpers.tpl
Normal file
62
petshop-chart/templates/_helpers.tpl
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "petshop-chart.chart" . }}
|
||||||
|
{{ include "petshop-chart.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "petshop-chart.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "petshop-chart.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "petshop-chart.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
72
petshop-chart/templates/deployment.yaml
Normal file
72
petshop-chart/templates/deployment.yaml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "petshop-chart.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "petshop-chart.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "petshop-chart.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8082
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: "OTEL_SERVICE_NAME"
|
||||||
|
value: "PetClinicWithAgent"
|
||||||
|
- name: "OTEL_EXPORTER_OTLP_ENDPOINT"
|
||||||
|
value: "http://digma-collector-api.digma.svc.cluster.local:5050"
|
||||||
|
- name: "OTEL_LOGS_EXPORTER"
|
||||||
|
value: "otlp"
|
||||||
|
- name: "CODE_PACKAGE_PREFIXES"
|
||||||
|
value: "org.springframework.samples.petclinic"
|
||||||
|
- name: "DEPLOYMENT_ENV"
|
||||||
|
value: "Load test"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
28
petshop-chart/templates/hpa.yaml
Normal file
28
petshop-chart/templates/hpa.yaml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2beta1
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "petshop-chart.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "petshop-chart.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
61
petshop-chart/templates/ingress.yaml
Normal file
61
petshop-chart/templates/ingress.yaml
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "petshop-chart.fullname" . -}}
|
||||||
|
{{- $svcPort := .Values.service.port -}}
|
||||||
|
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||||
|
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||||
|
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
service:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
port:
|
||||||
|
number: {{ $svcPort }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: {{ $svcPort }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
15
petshop-chart/templates/service.yaml
Normal file
15
petshop-chart/templates/service.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "petshop-chart.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: 8082
|
||||||
|
protocol: TCP
|
||||||
|
name: api
|
||||||
|
selector:
|
||||||
|
{{- include "petshop-chart.selectorLabels" . | nindent 4 }}
|
12
petshop-chart/templates/serviceaccount.yaml
Normal file
12
petshop-chart/templates/serviceaccount.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "petshop-chart.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
15
petshop-chart/templates/tests/test-connection.yaml
Normal file
15
petshop-chart/templates/tests/test-connection.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ include "petshop-chart.fullname" . }}-test-connection"
|
||||||
|
labels:
|
||||||
|
{{- include "petshop-chart.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: wget
|
||||||
|
image: busybox
|
||||||
|
command: ['wget']
|
||||||
|
args: ['{{ include "petshop-chart.fullname" . }}:{{ .Values.service.port }}']
|
||||||
|
restartPolicy: Never
|
82
petshop-chart/values.yaml
Normal file
82
petshop-chart/values.yaml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# Default values for petshop-chart.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: digmaai/petshop-app-deploy
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: 0.5.5
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
# Specifies whether a service account should be created
|
||||||
|
create: true
|
||||||
|
# Annotations to add to the service account
|
||||||
|
annotations: {}
|
||||||
|
# The name of the service account to use.
|
||||||
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
securityContext: {}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: LoadBalancer
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
className: ""
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
hosts:
|
||||||
|
- host: chart-example.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
tls: []
|
||||||
|
# - secretName: chart-example-tls
|
||||||
|
# hosts:
|
||||||
|
# - chart-example.local
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 5
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
# targetMemoryUtilizationPercentage: 80
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
22
pom.xml
22
pom.xml
|
@ -83,7 +83,11 @@
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<!-- caching -->
|
<!-- caching -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.cache</groupId>
|
<groupId>javax.cache</groupId>
|
||||||
|
@ -106,7 +110,11 @@
|
||||||
<version>${webjars-font-awesome.version}</version>
|
<version>${webjars-font-awesome.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- end of webjars -->
|
<!-- end of webjars -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.9.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
@ -117,6 +125,16 @@
|
||||||
<groupId>jakarta.xml.bind</groupId>
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||||
|
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
|
||||||
|
<version>1.26.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.vaadin.external.google</groupId>
|
||||||
|
<artifactId>android-json</artifactId>
|
||||||
|
<version>0.0.20131108.vaadin1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.springframework.samples.petclinic.adapters;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface PetVaccinationService {
|
||||||
|
@WithSpan
|
||||||
|
VaccinnationRecord[] AllVaccines() throws JSONException, IOException;
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
VaccinnationRecord VaccineRecord(int vaccinationRecordId) throws JSONException, IOException;
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.springframework.samples.petclinic.adapters;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PetVaccinationServiceFacade implements PetVaccinationService {
|
||||||
|
|
||||||
|
public static final String VACCINES_RECORDS_URL = "https://647f4bb4c246f166da9084c7.mockapi.io/api/vetcheck/vaccines";
|
||||||
|
|
||||||
|
private String MakeHttpCall(String url) throws IOException{
|
||||||
|
|
||||||
|
Request getAllVaccinesRequest = new Request.Builder().url(url).build();
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
Response getAllVaccinesResult = client.newCall(getAllVaccinesRequest).execute();
|
||||||
|
return getAllVaccinesResult.body().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@WithSpan
|
||||||
|
public VaccinnationRecord[] AllVaccines() throws JSONException, IOException {
|
||||||
|
|
||||||
|
var vaccineListString = MakeHttpCall(VACCINES_RECORDS_URL);
|
||||||
|
JSONArray jArr = new JSONArray(vaccineListString);
|
||||||
|
var vaccinnationRecords =
|
||||||
|
new ArrayList<VaccinnationRecord>();
|
||||||
|
|
||||||
|
for (int i = 0; i < jArr.length(); i++) {
|
||||||
|
|
||||||
|
VaccinnationRecord record = parseVaccinationRecord(jArr.getJSONObject(i));
|
||||||
|
vaccinnationRecords.add(record);
|
||||||
|
|
||||||
|
}
|
||||||
|
return vaccinnationRecords.toArray(VaccinnationRecord[]::new);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@WithSpan
|
||||||
|
public VaccinnationRecord VaccineRecord(int vaccinationRecordId) throws JSONException, IOException {
|
||||||
|
|
||||||
|
var idUrl = VACCINES_RECORDS_URL + "/" + vaccinationRecordId;
|
||||||
|
|
||||||
|
var vaccineListString = MakeHttpCall(idUrl);
|
||||||
|
|
||||||
|
JSONObject vaccineJson = new JSONObject(vaccineListString);
|
||||||
|
return parseVaccinationRecord(vaccineJson);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static VaccinnationRecord parseVaccinationRecord(JSONObject jsonObject) throws JSONException {
|
||||||
|
Integer petId = jsonObject.getInt("pet_id");
|
||||||
|
Integer id = jsonObject.getInt("id");
|
||||||
|
String vaccineDateString = jsonObject.getString("vaccine_date");
|
||||||
|
var vaccineDate = Instant.parse(vaccineDateString);
|
||||||
|
return new VaccinnationRecord(id, petId, vaccineDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.springframework.samples.petclinic.adapters;
|
||||||
|
|
||||||
|
public record VaccinnationRecord(int recordId, int petId, java.time.Instant vaccineDate) {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.api.trace.SpanKind;
|
||||||
|
import io.opentelemetry.api.trace.Tracer;
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
import org.springframework.samples.petclinic.owner.Owner;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class OwnerValidation {
|
||||||
|
|
||||||
|
private int counter = 0;
|
||||||
|
|
||||||
|
private UserValidationService usrValSvc;
|
||||||
|
|
||||||
|
private PasswordUtils pwdUtils;
|
||||||
|
|
||||||
|
private Tracer otelTracer;
|
||||||
|
|
||||||
|
private RoleService roleSvc;
|
||||||
|
|
||||||
|
private TwoFactorAuthenticationService twoFASvc;
|
||||||
|
|
||||||
|
public OwnerValidation(Tracer otelTracer) {
|
||||||
|
this.pwdUtils = new PasswordUtils();
|
||||||
|
this.roleSvc = new RoleService();
|
||||||
|
this.otelTracer = otelTracer;
|
||||||
|
this.usrValSvc = new UserValidationService();
|
||||||
|
this.twoFASvc = new TwoFactorAuthenticationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public void ValidateOwnerWithExternalService(Owner owner) {
|
||||||
|
|
||||||
|
this.AuthServiceValidateUser(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private void NewFunction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean UserNameMustStartWithR(String usr) {
|
||||||
|
|
||||||
|
if (!usr.toLowerCase().startsWith("r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
// This function and classes were generated by ChatGPT
|
||||||
|
public boolean ValidateUserAccess(String usr, String pswd, String sysCode) {
|
||||||
|
|
||||||
|
UserNameMustStartWithR(usr);
|
||||||
|
boolean vldUsr = usrValSvc.vldtUsr(usr);
|
||||||
|
if (!vldUsr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean vldPswd = pwdUtils.vldtPswd(usr, pswd);
|
||||||
|
if (!vldPswd) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean vldUsrRole = roleSvc.vldtUsrRole(usr, sysCode);
|
||||||
|
if (!vldUsrRole) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean is2FASuccess = twoFASvc.init2FA(usr);
|
||||||
|
if (!is2FASuccess) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean is2FATokenValid = false;
|
||||||
|
int retry = 0;
|
||||||
|
while (retry < 3 && !is2FATokenValid) {
|
||||||
|
String token = twoFASvc.getTokenInput();
|
||||||
|
is2FATokenValid = twoFASvc.vldtToken(usr, token);
|
||||||
|
retry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is2FATokenValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private synchronized void AuthServiceValidateUser(Owner owner) {
|
||||||
|
// This is the actual Root Cause!!
|
||||||
|
try {
|
||||||
|
Thread.sleep(4200 + ThreadLocalRandom.current().nextInt(90, 1100 + 1));
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean checkOwnerValidity(Owner owner) {
|
||||||
|
|
||||||
|
this.ValidateOwnerUserBad(owner);
|
||||||
|
return ValidateOwnerUser(owner);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private boolean ValidateOwnerUserBad(Owner owner) {
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
ValidateOwner();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private boolean ValidateOwnerUser(Owner owner) {
|
||||||
|
|
||||||
|
Span span = otelTracer.spanBuilder("db_access_01").startSpan();
|
||||||
|
|
||||||
|
var max = ThreadLocalRandom.current().nextInt(90, 110 + 1);
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
ValidateOwner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
span.end();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private void ValidateOwner() {
|
||||||
|
// simulate SpanKind of DB query
|
||||||
|
// see
|
||||||
|
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
|
||||||
|
Span span = otelTracer.spanBuilder("query_users_by_id")
|
||||||
|
.setSpanKind(SpanKind.CLIENT)
|
||||||
|
.setAttribute("db.system", "other_sql")
|
||||||
|
.setAttribute("db.statement", "select * from users where id = :id")
|
||||||
|
.startSpan();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(14);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
span.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public void PerformValidationFlow(Owner owner) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
|
||||||
|
public class PasswordUtils {
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean vldtPswd(String usr, String pswd) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public String encPswd(String pswd) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(300);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.samples.petclinic.adapters.PetVaccinationService;
|
||||||
|
import org.springframework.samples.petclinic.adapters.PetVaccinationServiceFacade;
|
||||||
|
import org.springframework.samples.petclinic.adapters.VaccinnationRecord;
|
||||||
|
import org.springframework.samples.petclinic.owner.Pet;
|
||||||
|
import org.springframework.samples.petclinic.owner.PetVaccine;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PetVaccinationStatusService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PetVaccinationService adapter;
|
||||||
|
|
||||||
|
public void UpdateVaccinationStatus(Pet[] pets){
|
||||||
|
|
||||||
|
for (Pet pet: pets){
|
||||||
|
try {
|
||||||
|
var vaccinationRecords = this.adapter.AllVaccines();
|
||||||
|
for (VaccinnationRecord record : vaccinationRecords){
|
||||||
|
|
||||||
|
var recordInfo = this.adapter.VaccineRecord(record.recordId());
|
||||||
|
if (recordInfo.petId()==pet.getId()){
|
||||||
|
var date = LocalDateTime.ofInstant(recordInfo.vaccineDate(), ZoneId.systemDefault());
|
||||||
|
PetVaccine petVaccine = new PetVaccine();
|
||||||
|
petVaccine.setDate(date.toLocalDate());
|
||||||
|
pet.addVaccine(petVaccine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (JSONException |IOException e) {
|
||||||
|
//Fail silently
|
||||||
|
Span.current().recordException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
|
||||||
|
public class RoleService {
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean vldtUsrRole(String usr, String sysCode) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(40);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
|
||||||
|
public class TwoFactorAuthenticationService {
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean init2FA(String usr) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(400);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenInput() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean vldtToken(String usr, String token) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(40);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.springframework.samples.petclinic.domain;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
|
||||||
|
public class UserValidationService {
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public boolean vldtUsr(String usr) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(300);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,7 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.samples.petclinic.owner;
|
package org.springframework.samples.petclinic.owner;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
|
@ -124,6 +128,27 @@ public class Owner extends Person {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVaccineExpired() {
|
||||||
|
|
||||||
|
for (Pet pet : getPets()) {
|
||||||
|
|
||||||
|
if (!pet.isNew()) {
|
||||||
|
|
||||||
|
for (PetVaccine vaccine : pet.getPetVaccines()) {
|
||||||
|
|
||||||
|
Duration duration = Duration.between(LocalDateTime.now(), vaccine.getDate());
|
||||||
|
if (duration.toDays() > 360) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Pet with the given name, or null if none found for this Owner.
|
* Return the Pet with the given name, or null if none found for this Owner.
|
||||||
* @param name to test
|
* @param name to test
|
||||||
|
|
|
@ -18,9 +18,11 @@ package org.springframework.samples.petclinic.owner;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.samples.petclinic.domain.OwnerValidation;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
|
@ -35,6 +37,8 @@ import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
import static io.opentelemetry.api.GlobalOpenTelemetry.getTracer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Ken Krebs
|
* @author Ken Krebs
|
||||||
|
@ -48,8 +52,14 @@ class OwnerController {
|
||||||
|
|
||||||
private final OwnerRepository owners;
|
private final OwnerRepository owners;
|
||||||
|
|
||||||
|
private OwnerValidation validator;
|
||||||
|
|
||||||
public OwnerController(OwnerRepository clinicService) {
|
public OwnerController(OwnerRepository clinicService) {
|
||||||
this.owners = clinicService;
|
this.owners = clinicService;
|
||||||
|
var otelTracer = getTracer("OwnerController");
|
||||||
|
|
||||||
|
validator = new OwnerValidation(otelTracer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitBinder
|
@InitBinder
|
||||||
|
@ -64,7 +74,10 @@ class OwnerController {
|
||||||
|
|
||||||
@GetMapping("/owners/new")
|
@GetMapping("/owners/new")
|
||||||
public String initCreationForm(Map<String, Object> model) {
|
public String initCreationForm(Map<String, Object> model) {
|
||||||
|
|
||||||
Owner owner = new Owner();
|
Owner owner = new Owner();
|
||||||
|
validator.ValidateOwnerWithExternalService(owner);
|
||||||
|
|
||||||
model.put("owner", owner);
|
model.put("owner", owner);
|
||||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||||
}
|
}
|
||||||
|
@ -74,8 +87,12 @@ class OwnerController {
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||||
}
|
}
|
||||||
|
validator.ValidateOwnerWithExternalService(owner);
|
||||||
|
validator.PerformValidationFlow(owner);
|
||||||
|
|
||||||
|
validator.checkOwnerValidity(owner);
|
||||||
this.owners.save(owner);
|
this.owners.save(owner);
|
||||||
|
validator.ValidateUserAccess("admin", "pwd", "fullaccess");
|
||||||
return "redirect:/owners/" + owner.getId();
|
return "redirect:/owners/" + owner.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +104,8 @@ class OwnerController {
|
||||||
@GetMapping("/owners")
|
@GetMapping("/owners")
|
||||||
public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
|
public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
|
||||||
Model model) {
|
Model model) {
|
||||||
|
validator.ValidateUserAccess("admin", "pwd", "fullaccess");
|
||||||
|
|
||||||
// allow parameterless GET request for /owners to return all records
|
// allow parameterless GET request for /owners to return all records
|
||||||
if (owner.getLastName() == null) {
|
if (owner.getLastName() == null) {
|
||||||
owner.setLastName(""); // empty string signifies broadest possible search
|
owner.setLastName(""); // empty string signifies broadest possible search
|
||||||
|
@ -110,6 +129,11 @@ class OwnerController {
|
||||||
return addPaginationModel(page, model, ownersResults);
|
return addPaginationModel(page, model, ownersResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String addPaginationModel(int page, Model model, Page<Owner> paginated) {
|
private String addPaginationModel(int page, Model model, Page<Owner> paginated) {
|
||||||
model.addAttribute("listOwners", paginated);
|
model.addAttribute("listOwners", paginated);
|
||||||
List<Owner> listOwners = paginated.getContent();
|
List<Owner> listOwners = paginated.getContent();
|
||||||
|
@ -120,12 +144,18 @@ class OwnerController {
|
||||||
return "owners/ownersList";
|
return "owners/ownersList";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
private Page<Owner> findPaginatedForOwnersLastName(int page, String lastname) {
|
private Page<Owner> findPaginatedForOwnersLastName(int page, String lastname) {
|
||||||
int pageSize = 5;
|
int pageSize = 25;
|
||||||
Pageable pageable = PageRequest.of(page - 1, pageSize);
|
Pageable pageable = PageRequest.of(page - 1, pageSize);
|
||||||
return owners.findByLastName(lastname, pageable);
|
return owners.findByLastName(lastname, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/owners/{ownerId}/edit")
|
@GetMapping("/owners/{ownerId}/edit")
|
||||||
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
|
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
|
||||||
Owner owner = this.owners.findById(ownerId);
|
Owner owner = this.owners.findById(ownerId);
|
||||||
|
@ -133,6 +163,24 @@ class OwnerController {
|
||||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/owners/{ownerId}/edit")
|
@PostMapping("/owners/{ownerId}/edit")
|
||||||
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
|
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
|
||||||
@PathVariable("ownerId") int ownerId) {
|
@PathVariable("ownerId") int ownerId) {
|
||||||
|
@ -151,8 +199,10 @@ class OwnerController {
|
||||||
* @return a ModelMap with the model attributes for the view
|
* @return a ModelMap with the model attributes for the view
|
||||||
*/
|
*/
|
||||||
@GetMapping("/owners/{ownerId}")
|
@GetMapping("/owners/{ownerId}")
|
||||||
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
|
public ModelAndView showOwner(@PathVariable("ownerId")
|
||||||
ModelAndView mav = new ModelAndView("owners/ownerDetails");
|
int ownerId) {
|
||||||
|
ModelAndView mav =
|
||||||
|
new ModelAndView("owners/ownerDetails");
|
||||||
Owner owner = this.owners.findById(ownerId);
|
Owner owner = this.owners.findById(ownerId);
|
||||||
mav.addObject(owner);
|
mav.addObject(owner);
|
||||||
return mav;
|
return mav;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.samples.petclinic.model.NamedEntity;
|
import org.springframework.samples.petclinic.model.NamedEntity;
|
||||||
|
|
||||||
|
@ -57,6 +58,11 @@ public class Pet extends NamedEntity {
|
||||||
@OrderBy("visit_date ASC")
|
@OrderBy("visit_date ASC")
|
||||||
private Set<Visit> visits = new LinkedHashSet<>();
|
private Set<Visit> visits = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "pet_id")
|
||||||
|
@OrderBy("vaccine_date ASC")
|
||||||
|
private Set<PetVaccine> pet_vaccines = new LinkedHashSet<>();
|
||||||
|
|
||||||
public void setBirthDate(LocalDate birthDate) {
|
public void setBirthDate(LocalDate birthDate) {
|
||||||
this.birthDate = birthDate;
|
this.birthDate = birthDate;
|
||||||
}
|
}
|
||||||
|
@ -77,8 +83,16 @@ public class Pet extends NamedEntity {
|
||||||
return this.visits;
|
return this.visits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<PetVaccine> getPetVaccines() {
|
||||||
|
return this.pet_vaccines;
|
||||||
|
}
|
||||||
|
|
||||||
public void addVisit(Visit visit) {
|
public void addVisit(Visit visit) {
|
||||||
getVisits().add(visit);
|
getVisits().add(visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addVaccine(PetVaccine vaccine) {
|
||||||
|
getPetVaccines().add(vaccine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ package org.springframework.samples.petclinic.owner;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.samples.petclinic.domain.PetVaccinationStatusService;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
@ -44,6 +46,9 @@ class PetController {
|
||||||
|
|
||||||
private final OwnerRepository owners;
|
private final OwnerRepository owners;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PetVaccinationStatusService petVaccinationStatus;
|
||||||
|
|
||||||
public PetController(OwnerRepository owners) {
|
public PetController(OwnerRepository owners) {
|
||||||
this.owners = owners;
|
this.owners = owners;
|
||||||
}
|
}
|
||||||
|
@ -82,9 +87,20 @@ class PetController {
|
||||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/pets/new")
|
@PostMapping("/pets/new")
|
||||||
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
|
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
|
||||||
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
|
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
|
||||||
result.rejectValue("name", "duplicate", "already exists");
|
result.rejectValue("name", "duplicate", "already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +111,18 @@ class PetController {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.owners.save(owner);
|
this.owners.save(owner);
|
||||||
|
petVaccinationStatus.UpdateVaccinationStatus(owner.getPets().toArray(Pet[]::new));
|
||||||
|
|
||||||
return "redirect:/owners/{ownerId}";
|
return "redirect:/owners/{ownerId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/pets/{petId}/edit")
|
@GetMapping("/pets/{petId}/edit")
|
||||||
public String initUpdateForm(Owner owner, @PathVariable("petId") int petId, ModelMap model) {
|
public String initUpdateForm(Owner owner, @PathVariable("petId") int petId, ModelMap model) {
|
||||||
Pet pet = owner.getPet(petId);
|
Pet pet = owner.getPet(petId);
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.springframework.samples.petclinic.owner;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.samples.petclinic.model.BaseEntity;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple JavaBean domain object representing a visit.
|
||||||
|
*
|
||||||
|
* @author Ken Krebs
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "pet_vaccines")
|
||||||
|
public class PetVaccine extends BaseEntity {
|
||||||
|
|
||||||
|
@Column(name = "vaccine_date")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of Visit for the current date
|
||||||
|
*/
|
||||||
|
public PetVaccine() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(LocalDate date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,10 +15,18 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.samples.petclinic.owner;
|
package org.springframework.samples.petclinic.owner;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.Errors;
|
import org.springframework.validation.Errors;
|
||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>Validator</code> for <code>Pet</code> forms.
|
* <code>Validator</code> for <code>Pet</code> forms.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -51,6 +59,35 @@ public class PetValidator implements Validator {
|
||||||
if (pet.getBirthDate() == null) {
|
if (pet.getBirthDate() == null) {
|
||||||
errors.rejectValue("birthDate", REQUIRED, REQUIRED);
|
errors.rejectValue("birthDate", REQUIRED, REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateVaccinationStatus(pet);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
private boolean updateVaccinationStatus(Pet pet) throws IOException {
|
||||||
|
// OkHttpClient client = new OkHttpClient();
|
||||||
|
//
|
||||||
|
// Request request = new
|
||||||
|
// Request.Builder().url("https://647f4bb4c246f166da9084c7.mockapi.io/api/vetcheck/vaccines")
|
||||||
|
// .build();
|
||||||
|
//
|
||||||
|
// String responseText = "";
|
||||||
|
// try (Response response = client.newCall(request).execute()) {
|
||||||
|
// responseText = response.body().string();
|
||||||
|
// JSONObject Jobject = new JSONObject(responseText);
|
||||||
|
// JSONArray Jarray = Jobject.getJSONArray("employees");
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// catch (JSONException e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
database=postgres
|
database=postgres
|
||||||
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic}
|
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/postgres}
|
||||||
spring.datasource.username=${POSTGRES_USER:petclinic}
|
spring.datasource.username=${POSTGRES_USER:petclinic}
|
||||||
spring.datasource.password=${POSTGRES_PASS:petclinic}
|
spring.datasource.password=${POSTGRES_PASS:petclinic}
|
||||||
# SQL is written to be idempotent so this is safe
|
# SQL is written to be idempotent so this is safe
|
||||||
|
|
|
@ -15,6 +15,11 @@ spring.messages.basename=messages/messages
|
||||||
|
|
||||||
# Actuator
|
# Actuator
|
||||||
management.endpoints.web.exposure.include=*
|
management.endpoints.web.exposure.include=*
|
||||||
|
management.metrics.distribution.slo.http.server.requests=50ms, 100ms, 200ms, 400ms
|
||||||
|
management.metrics.distribution.percentiles.http.server.requests=0.5, 0.9, 0.95, 0.99, 0.999
|
||||||
|
management.metrics.web.server.request.autotime.percentiles=0.95
|
||||||
|
management.metrics.distribution.percentiles-histogram.http.server.requests=true
|
||||||
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
logging.level.org.springframework=INFO
|
logging.level.org.springframework=INFO
|
||||||
|
@ -23,3 +28,4 @@ logging.level.org.springframework=INFO
|
||||||
|
|
||||||
# Maximum time static resources should be cached
|
# Maximum time static resources should be cached
|
||||||
spring.web.resources.cache.cachecontrol.max-age=12h
|
spring.web.resources.cache.cachecontrol.max-age=12h
|
||||||
|
server.port=8082
|
||||||
|
|
|
@ -34,6 +34,18 @@ INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail'
|
||||||
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||||
|
|
||||||
INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1);
|
INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Mocka', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Abby', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Tesla', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Freer', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Castro', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Mohawk', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Jerome', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Niley', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Timmy', '2010-09-07', 1, 1);
|
||||||
|
INSERT INTO pets VALUES (default, 'Bob', '2010-09-07', 1, 1);
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2);
|
INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2);
|
||||||
INSERT INTO pets VALUES (default, 'Rosy', '2011-04-17', 2, 3);
|
INSERT INTO pets VALUES (default, 'Rosy', '2011-04-17', 2, 3);
|
||||||
INSERT INTO pets VALUES (default, 'Jewel', '2010-03-07', 2, 3);
|
INSERT INTO pets VALUES (default, 'Jewel', '2010-03-07', 2, 3);
|
||||||
|
|
|
@ -62,3 +62,12 @@ CREATE TABLE visits (
|
||||||
);
|
);
|
||||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||||
|
|
||||||
|
CREATE TABLE pet_vaccines (
|
||||||
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
|
pet_id INTEGER,
|
||||||
|
vaccine_date DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE pet_vaccines ADD CONSTRAINT fk_pet_vaccines_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||||
|
CREATE INDEX pet_vaccines_pet_id ON pet_vaccines (pet_id);
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1);
|
INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1);
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2);
|
INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2);
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3);
|
INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3);
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4);
|
INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4);
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5);
|
INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5);
|
||||||
INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6);
|
INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6);
|
||||||
|
|
||||||
INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology');
|
INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology');
|
||||||
INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery');
|
INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery');
|
||||||
INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry');
|
INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry');
|
||||||
|
|
||||||
INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||||
INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||||
INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||||
INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||||
INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||||
|
|
||||||
INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||||
INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dog');
|
INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dog');
|
||||||
INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='lizard');
|
INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='lizard');
|
||||||
INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='snake');
|
INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='snake');
|
||||||
INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='bird');
|
INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='bird');
|
||||||
INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||||
|
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9);
|
||||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10);
|
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10);
|
||||||
|
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12);
|
||||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13);
|
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13);
|
||||||
|
|
||||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1);
|
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1);
|
||||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2);
|
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2);
|
||||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3);
|
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3);
|
||||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4);
|
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4);
|
||||||
|
|
|
@ -50,3 +50,9 @@ CREATE TABLE IF NOT EXISTS visits (
|
||||||
description TEXT
|
description TEXT
|
||||||
);
|
);
|
||||||
CREATE INDEX ON visits (pet_id);
|
CREATE INDEX ON visits (pet_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pet_vaccines (
|
||||||
|
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
|
pet_id INT REFERENCES pets (id),
|
||||||
|
vaccine_date DATE,
|
||||||
|
);
|
||||||
|
|
|
@ -26,6 +26,11 @@
|
||||||
<th>Telephone</th>
|
<th>Telephone</th>
|
||||||
<td th:text="*{telephone}"></td>
|
<td th:text="*{telephone}"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Needs Vaccine</th>
|
||||||
|
<td th:text="*{isVaccineExpired()}"></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary">Edit
|
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary">Edit
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.samples.petclinic.owner;
|
|
||||||
|
|
||||||
import org.assertj.core.util.Lists;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.context.annotation.FilterType;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
|
||||||
|
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test class for the {@link PetController}
|
|
||||||
*
|
|
||||||
* @author Colin But
|
|
||||||
*/
|
|
||||||
@WebMvcTest(value = PetController.class,
|
|
||||||
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
|
|
||||||
class PetControllerTests {
|
|
||||||
|
|
||||||
private static final int TEST_OWNER_ID = 1;
|
|
||||||
|
|
||||||
private static final int TEST_PET_ID = 1;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MockMvc mockMvc;
|
|
||||||
|
|
||||||
@MockBean
|
|
||||||
private OwnerRepository owners;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setup() {
|
|
||||||
PetType cat = new PetType();
|
|
||||||
cat.setId(3);
|
|
||||||
cat.setName("hamster");
|
|
||||||
given(this.owners.findPetTypes()).willReturn(Lists.newArrayList(cat));
|
|
||||||
Owner owner = new Owner();
|
|
||||||
Pet pet = new Pet();
|
|
||||||
owner.addPet(pet);
|
|
||||||
pet.setId(TEST_PET_ID);
|
|
||||||
given(this.owners.findById(TEST_OWNER_ID)).willReturn(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testInitCreationForm() throws Exception {
|
|
||||||
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID))
|
|
||||||
.andExpect(status().isOk())
|
|
||||||
.andExpect(view().name("pets/createOrUpdatePetForm"))
|
|
||||||
.andExpect(model().attributeExists("pet"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testProcessCreationFormSuccess() throws Exception {
|
|
||||||
mockMvc
|
|
||||||
.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
|
|
||||||
.param("type", "hamster")
|
|
||||||
.param("birthDate", "2015-02-12"))
|
|
||||||
.andExpect(status().is3xxRedirection())
|
|
||||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testProcessCreationFormHasErrors() throws Exception {
|
|
||||||
mockMvc
|
|
||||||
.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
|
|
||||||
.param("birthDate", "2015-02-12"))
|
|
||||||
.andExpect(model().attributeHasNoErrors("owner"))
|
|
||||||
.andExpect(model().attributeHasErrors("pet"))
|
|
||||||
.andExpect(model().attributeHasFieldErrors("pet", "type"))
|
|
||||||
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required"))
|
|
||||||
.andExpect(status().isOk())
|
|
||||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testInitUpdateForm() throws Exception {
|
|
||||||
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
|
|
||||||
.andExpect(status().isOk())
|
|
||||||
.andExpect(model().attributeExists("pet"))
|
|
||||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testProcessUpdateFormSuccess() throws Exception {
|
|
||||||
mockMvc
|
|
||||||
.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
|
|
||||||
.param("type", "hamster")
|
|
||||||
.param("birthDate", "2015-02-12"))
|
|
||||||
.andExpect(status().is3xxRedirection())
|
|
||||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testProcessUpdateFormHasErrors() throws Exception {
|
|
||||||
mockMvc
|
|
||||||
.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
|
|
||||||
.param("birthDate", "2015/02/12"))
|
|
||||||
.andExpect(model().attributeHasNoErrors("owner"))
|
|
||||||
.andExpect(model().attributeHasErrors("pet"))
|
|
||||||
.andExpect(status().isOk())
|
|
||||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,228 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.samples.petclinic.service;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
|
||||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
|
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.samples.petclinic.owner.Owner;
|
|
||||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
|
||||||
import org.springframework.samples.petclinic.owner.Pet;
|
|
||||||
import org.springframework.samples.petclinic.owner.PetType;
|
|
||||||
import org.springframework.samples.petclinic.owner.Visit;
|
|
||||||
import org.springframework.samples.petclinic.vet.Vet;
|
|
||||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test of the Service and the Repository layer.
|
|
||||||
* <p>
|
|
||||||
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
|
|
||||||
* by the Spring TestContext Framework:
|
|
||||||
* </p>
|
|
||||||
* <ul>
|
|
||||||
* <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
|
|
||||||
* time between test execution.</li>
|
|
||||||
* <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
|
|
||||||
* don't need to perform application context lookups. See the use of
|
|
||||||
* {@link Autowired @Autowired} on the <code> </code> instance variable, which uses
|
|
||||||
* autowiring <em>by type</em>.
|
|
||||||
* <li><strong>Transaction management</strong>, meaning each test method is executed in
|
|
||||||
* its own transaction, which is automatically rolled back by default. Thus, even if tests
|
|
||||||
* insert or otherwise change database state, there is no need for a teardown or cleanup
|
|
||||||
* script.
|
|
||||||
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
|
|
||||||
* also inherited and can be used for explicit bean lookup if necessary.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author Ken Krebs
|
|
||||||
* @author Rod Johnson
|
|
||||||
* @author Juergen Hoeller
|
|
||||||
* @author Sam Brannen
|
|
||||||
* @author Michael Isvy
|
|
||||||
* @author Dave Syer
|
|
||||||
*/
|
|
||||||
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
|
|
||||||
// Ensure that if the mysql profile is active we connect to the real database:
|
|
||||||
@AutoConfigureTestDatabase(replace = Replace.NONE)
|
|
||||||
// @TestPropertySource("/application-postgres.properties")
|
|
||||||
class ClinicServiceTests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected OwnerRepository owners;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected VetRepository vets;
|
|
||||||
|
|
||||||
Pageable pageable;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldFindOwnersByLastName() {
|
|
||||||
Page<Owner> owners = this.owners.findByLastName("Davis", pageable);
|
|
||||||
assertThat(owners).hasSize(2);
|
|
||||||
|
|
||||||
owners = this.owners.findByLastName("Daviss", pageable);
|
|
||||||
assertThat(owners).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldFindSingleOwnerWithPet() {
|
|
||||||
Owner owner = this.owners.findById(1);
|
|
||||||
assertThat(owner.getLastName()).startsWith("Franklin");
|
|
||||||
assertThat(owner.getPets()).hasSize(1);
|
|
||||||
assertThat(owner.getPets().get(0).getType()).isNotNull();
|
|
||||||
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Transactional
|
|
||||||
void shouldInsertOwner() {
|
|
||||||
Page<Owner> owners = this.owners.findByLastName("Schultz", pageable);
|
|
||||||
int found = (int) owners.getTotalElements();
|
|
||||||
|
|
||||||
Owner owner = new Owner();
|
|
||||||
owner.setFirstName("Sam");
|
|
||||||
owner.setLastName("Schultz");
|
|
||||||
owner.setAddress("4, Evans Street");
|
|
||||||
owner.setCity("Wollongong");
|
|
||||||
owner.setTelephone("4444444444");
|
|
||||||
this.owners.save(owner);
|
|
||||||
assertThat(owner.getId().longValue()).isNotEqualTo(0);
|
|
||||||
|
|
||||||
owners = this.owners.findByLastName("Schultz", pageable);
|
|
||||||
assertThat(owners.getTotalElements()).isEqualTo(found + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Transactional
|
|
||||||
void shouldUpdateOwner() {
|
|
||||||
Owner owner = this.owners.findById(1);
|
|
||||||
String oldLastName = owner.getLastName();
|
|
||||||
String newLastName = oldLastName + "X";
|
|
||||||
|
|
||||||
owner.setLastName(newLastName);
|
|
||||||
this.owners.save(owner);
|
|
||||||
|
|
||||||
// retrieving new name from database
|
|
||||||
owner = this.owners.findById(1);
|
|
||||||
assertThat(owner.getLastName()).isEqualTo(newLastName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldFindAllPetTypes() {
|
|
||||||
Collection<PetType> petTypes = this.owners.findPetTypes();
|
|
||||||
|
|
||||||
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
|
|
||||||
assertThat(petType1.getName()).isEqualTo("cat");
|
|
||||||
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
|
|
||||||
assertThat(petType4.getName()).isEqualTo("snake");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Transactional
|
|
||||||
void shouldInsertPetIntoDatabaseAndGenerateId() {
|
|
||||||
Owner owner6 = this.owners.findById(6);
|
|
||||||
int found = owner6.getPets().size();
|
|
||||||
|
|
||||||
Pet pet = new Pet();
|
|
||||||
pet.setName("bowser");
|
|
||||||
Collection<PetType> types = this.owners.findPetTypes();
|
|
||||||
pet.setType(EntityUtils.getById(types, PetType.class, 2));
|
|
||||||
pet.setBirthDate(LocalDate.now());
|
|
||||||
owner6.addPet(pet);
|
|
||||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
|
||||||
|
|
||||||
this.owners.save(owner6);
|
|
||||||
|
|
||||||
owner6 = this.owners.findById(6);
|
|
||||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
|
||||||
// checks that id has been generated
|
|
||||||
pet = owner6.getPet("bowser");
|
|
||||||
assertThat(pet.getId()).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Transactional
|
|
||||||
void shouldUpdatePetName() throws Exception {
|
|
||||||
Owner owner6 = this.owners.findById(6);
|
|
||||||
Pet pet7 = owner6.getPet(7);
|
|
||||||
String oldName = pet7.getName();
|
|
||||||
|
|
||||||
String newName = oldName + "X";
|
|
||||||
pet7.setName(newName);
|
|
||||||
this.owners.save(owner6);
|
|
||||||
|
|
||||||
owner6 = this.owners.findById(6);
|
|
||||||
pet7 = owner6.getPet(7);
|
|
||||||
assertThat(pet7.getName()).isEqualTo(newName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldFindVets() {
|
|
||||||
Collection<Vet> vets = this.vets.findAll();
|
|
||||||
|
|
||||||
Vet vet = EntityUtils.getById(vets, Vet.class, 3);
|
|
||||||
assertThat(vet.getLastName()).isEqualTo("Douglas");
|
|
||||||
assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
|
|
||||||
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
|
|
||||||
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Transactional
|
|
||||||
void shouldAddNewVisitForPet() {
|
|
||||||
Owner owner6 = this.owners.findById(6);
|
|
||||||
Pet pet7 = owner6.getPet(7);
|
|
||||||
int found = pet7.getVisits().size();
|
|
||||||
Visit visit = new Visit();
|
|
||||||
visit.setDescription("test");
|
|
||||||
|
|
||||||
owner6.addVisit(pet7.getId(), visit);
|
|
||||||
this.owners.save(owner6);
|
|
||||||
|
|
||||||
owner6 = this.owners.findById(6);
|
|
||||||
|
|
||||||
assertThat(pet7.getVisits()) //
|
|
||||||
.hasSize(found + 1) //
|
|
||||||
.allMatch(value -> value.getId() != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldFindVisitsByPetId() throws Exception {
|
|
||||||
Owner owner6 = this.owners.findById(6);
|
|
||||||
Pet pet7 = owner6.getPet(7);
|
|
||||||
Collection<Visit> visits = pet7.getVisits();
|
|
||||||
|
|
||||||
assertThat(visits) //
|
|
||||||
.hasSize(2) //
|
|
||||||
.element(0)
|
|
||||||
.extracting(Visit::getDate)
|
|
||||||
.isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -9,12 +9,12 @@
|
||||||
<collectionProp name="Arguments.arguments">
|
<collectionProp name="Arguments.arguments">
|
||||||
<elementProp name="PETCLINIC_HOST" elementType="Argument">
|
<elementProp name="PETCLINIC_HOST" elementType="Argument">
|
||||||
<stringProp name="Argument.name">PETCLINIC_HOST</stringProp>
|
<stringProp name="Argument.name">PETCLINIC_HOST</stringProp>
|
||||||
<stringProp name="Argument.value">localhost</stringProp>
|
<stringProp name="Argument.value">a25c892af46df47589102bdef28fda12-1374796034.eu-west-1.elb.amazonaws.com</stringProp>
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
<stringProp name="Argument.metadata">=</stringProp>
|
||||||
</elementProp>
|
</elementProp>
|
||||||
<elementProp name="PETCLINIC_PORT" elementType="Argument">
|
<elementProp name="PETCLINIC_PORT" elementType="Argument">
|
||||||
<stringProp name="Argument.name">PETCLINIC_PORT</stringProp>
|
<stringProp name="Argument.name">PETCLINIC_PORT</stringProp>
|
||||||
<stringProp name="Argument.value">8080</stringProp>
|
<stringProp name="Argument.value">80</stringProp>
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
<stringProp name="Argument.metadata">=</stringProp>
|
||||||
</elementProp>
|
</elementProp>
|
||||||
<elementProp name="CONTEXT_WEB" elementType="Argument">
|
<elementProp name="CONTEXT_WEB" elementType="Argument">
|
||||||
|
|
Loading…
Reference in a new issue