`scripts/simulate-teacher.mjs` creates, edits and deletes what teachers create — course, room, topic, task, board, columns, cards, rich text, link, Etherpad pad, folder, files — so the MCP server can be exercised against content the real account has never held. It is the only thing here that writes to a Schulcloud and refuses any non-localhost address. `scripts/mcp-env.sh` points the server and CLI at the instance. Two gaps it exposed in the stack itself: The files-storage AMQP consumer is a separate entrypoint, and we were running only the HTTP one. Nothing was bound to the `files-storage` exchange, so `TaskService.delete` — which awaits deleteFilesOfParent over AMQP before touching the task — hung until the request timeout. Deleting any task or topic answered 408 with the entity still there. The demo data is dated 2017-2018 and the v3 endpoints filter on those dates, so a student saw no tasks at all. seed.sh now brings courses and homework into the present, which is the difference between a fixture that exercises the student-facing surface and one that looks empty. Teams turn out to be uncreatable through the API (the legacy service registers no `create`, v3 has no route), and a new board is unpublished and 403s for students, so the simulation adopts a seeded team and leaves one board a draft on purpose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
107 lines
4.6 KiB
Bash
Executable File
107 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Seed the local instance with the upstream demo school.
|
|
#
|
|
# This is what the real deployment's init job does (dof_app_deploy
|
|
# .../schulcloud-server-init/templates/configmap_file_init.yml.j2): it asks the
|
|
# management app to load backup/setup/*.json, which ships inside the server
|
|
# image. Safe to re-run — collections are replaced, not appended to.
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
MGMT=http://127.0.0.1:3333/api/management/database
|
|
COMPOSE=(docker compose)
|
|
|
|
echo "==> waiting for the management app"
|
|
for _ in $(seq 1 60); do
|
|
if curl -fsS -o /dev/null "$MGMT/../../docs" 2>/dev/null || curl -fsS -o /dev/null -X POST "$MGMT/sync-indexes" 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
echo "==> seeding collections (this takes a minute or two)"
|
|
curl -fsS --retry 30 --retry-all-errors --retry-delay 10 \
|
|
-X POST "$MGMT/seed?with-indexes=true" >/dev/null
|
|
echo " done"
|
|
|
|
# The legacy file service (course/topic attachments, as opposed to the newer
|
|
# /api/v3/file API) reads its S3 credentials from a storageproviders document
|
|
# rather than from the environment, and there is deliberately no seed data for
|
|
# it. Without this, legacy uploads fail with a provider-not-found error.
|
|
echo "==> registering MinIO as the legacy storage provider"
|
|
S3_KEY=$(grep -E '^S3_KEY=' env/shared.env | cut -d= -f2-)
|
|
SECRET=$(curl -fsS -X POST "$MGMT/encrypt-plain-text" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "$(printf '{"plainText":"miniouser","key":"%s"}' "$S3_KEY")")
|
|
|
|
"${COMPOSE[@]}" exec -T mongo mongosh schulcloud --quiet --eval "
|
|
const id = ObjectId('62949a4003839b6162aa566b');
|
|
db.storageproviders.replaceOne({ _id: id }, {
|
|
_id: id, isShared: true, region: 'eu-central-1', type: 'S3',
|
|
endpointUrl: 'http://minio:9000',
|
|
accessKeyId: 'miniouser',
|
|
secretAccessKey: '$SECRET',
|
|
maxBuckets: 150, freeBuckets: 138,
|
|
createdAt: new Date(), updatedAt: new Date(), __v: 0,
|
|
}, { upsert: true });
|
|
const r = db.schools.updateMany({}, { \$set: { storageProvider: id } });
|
|
print(' schools linked to the provider: ' + r.modifiedCount);
|
|
"
|
|
|
|
# The demo data is dated 2017-2018, and the v3 endpoints filter on those dates:
|
|
# a course that has ended shows a student no tasks, and a task whose due date
|
|
# passed years ago is in neither the open nor the finished list and has dropped
|
|
# off the course page. The result is a student account that looks empty for
|
|
# reasons that have nothing to do with whatever is being tested against it.
|
|
#
|
|
# So move the stale data into the present. Courses get a term around today.
|
|
# Homework is shifted by one offset per date cluster, which keeps the relative
|
|
# order — and so which tasks are past due, the ones carrying the graded
|
|
# submissions — while landing the newest of them two weeks ago.
|
|
echo "==> dating the demo data to the present"
|
|
"${COMPOSE[@]}" exec -T mongo mongosh schulcloud --quiet --eval "
|
|
const now = new Date();
|
|
const courses = db.courses.updateMany({ untilDate: { \$lt: now } }, { \$set: {
|
|
startDate: new Date(now.getTime() - 180 * 86400000),
|
|
untilDate: new Date(now.getTime() + 185 * 86400000),
|
|
} });
|
|
print(' courses given a current term: ' + courses.modifiedCount);
|
|
|
|
let moved = 0;
|
|
for (let pass = 0; pass < 10; pass++) {
|
|
const cutoff = new Date(Date.now() - 365 * 86400000);
|
|
const newest = db.homeworks.find({ dueDate: { \$lt: cutoff } }).sort({ dueDate: -1 }).limit(1).toArray()[0];
|
|
if (!newest) break;
|
|
const offset = (Date.now() - 14 * 86400000) - newest.dueDate.getTime();
|
|
db.homeworks.find({ dueDate: { \$lt: cutoff } }).forEach((h) => {
|
|
const set = {};
|
|
for (const field of ['dueDate', 'availableDate', 'createdAt', 'updatedAt']) {
|
|
if (h[field] instanceof Date) set[field] = new Date(h[field].getTime() + offset);
|
|
}
|
|
db.homeworks.updateOne({ _id: h._id }, { \$set: set });
|
|
moved++;
|
|
});
|
|
}
|
|
print(' homework brought forward: ' + moved);
|
|
"
|
|
|
|
cat <<'ACCOUNTS'
|
|
|
|
==> ready — http://localhost:4400
|
|
|
|
Seeded accounts (the demo password differs by account — upstream quirk):
|
|
|
|
demo-schueler@schul-cloud.org student Fritz Schmidt schulcloud
|
|
^ has graded submissions, incl. a feedback-only and a 100% one
|
|
demo-lehrer@schul-cloud.org teacher Erika Meier schulcloud
|
|
klara.fall@schul-cloud.org teacher Klara Fall Schulcloud1!
|
|
^ owns Fritz's graded Biologie submissions
|
|
lehrer@schul-cloud.org teacher Cord Carl Schulcloud1!
|
|
admin@schul-cloud.org admin Thorsten Test Schulcloud1!
|
|
*.qa@schul-cloud.org various Schulcloud1qa!
|
|
|
|
Sign in as the teacher to grade, as the student to see what grading looks
|
|
like from the side our MCP server reads.
|
|
ACCOUNTS
|